/
wml-flow.lua
262 lines (236 loc) · 7.59 KB
/
wml-flow.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
local helper = wesnoth.require "helper"
local utils = wesnoth.require "wml-utils"
local wml_actions = wesnoth.wml_actions
function wml_actions.command(cfg)
utils.handle_event_commands(cfg, "plain")
end
-- we can't create functions with names that are Lua keywords (eg if, while)
-- instead, we store the following anonymous functions directly into
-- the table, using the [] operator, rather than by using the point syntax
wml_actions["if"] = function(cfg)
if not (wml.get_child(cfg, 'then') or wml.get_child(cfg, 'elseif') or wml.get_child(cfg, 'else')) then
helper.wml_error("[if] didn't find any [then], [elseif], or [else] children.")
end
if wesnoth.eval_conditional(cfg) then -- evaluate [if] tag
for then_child in wml.child_range(cfg, "then") do
local action = utils.handle_event_commands(then_child, "conditional")
if action ~= "none" then break end
end
return -- stop after executing [then] tags
end
for elseif_child in wml.child_range(cfg, "elseif") do
if wesnoth.eval_conditional(elseif_child) then -- we'll evaluate the [elseif] tags one by one
for then_tag in wml.child_range(elseif_child, "then") do
local action = utils.handle_event_commands(then_tag, "conditional")
if action ~= "none" then break end
end
return -- stop on first matched condition
end
end
-- no matched condition, try the [else] tags
for else_child in wml.child_range(cfg, "else") do
local action = utils.handle_event_commands(else_child, "conditional")
if action ~= "none" then break end
end
end
wml_actions["while"] = function( cfg )
if wml.child_count(cfg, "do") == 0 then
helper.wml_error "[while] does not contain any [do] tags"
end
-- execute [do] up to 65536 times
for i = 1, 65536 do
if wesnoth.eval_conditional( cfg ) then
for do_child in wml.child_range( cfg, "do" ) do
local action = utils.handle_event_commands(do_child, "loop")
if action == "break" then
utils.set_exiting("none")
return
elseif action == "continue" then
utils.set_exiting("none")
break
elseif action ~= "none" then
return
end
end
else return end
end
end
wml_actions["break"] = function(cfg)
utils.set_exiting("break")
end
wml_actions["return"] = function(cfg)
utils.set_exiting("return")
end
function wml_actions.continue(cfg)
utils.set_exiting("continue")
end
wesnoth.wml_actions["for"] = function(cfg)
if wml.child_count(cfg, "do") == 0 then
helper.wml_error "[for] does not contain any [do] tags"
end
local loop_lim = {}
local first
if cfg.array ~= nil then
if cfg.reverse then
first = wml.variables[cfg.array .. ".length"] - 1
loop_lim.last = 0
loop_lim.step = -1
else
first = 0
loop_lim.last = '$($' .. cfg.array .. ".length - 1)"
loop_lim.step = 1
end
else
-- Get a literal config to fetch end and step;
-- this done is to delay expansion of variables
local cfg_lit = wml.literal(cfg)
first = cfg.start or 0
loop_lim.last = cfg_lit["end"] or first
loop_lim.step = cfg_lit.step or 1
end
loop_lim = wml.tovconfig(loop_lim)
if loop_lim.step == 0 then -- Sanity check
helper.wml_error("[for] has a step of 0!")
end
if (first < loop_lim.last and loop_lim.step <= 0)
or (first > loop_lim.last and loop_lim.step >= 0) then
-- Sanity check: If they specify something like start,end,step=1,4,-1
-- then we do nothing
return
end
local i_var = cfg.variable or "i"
local save_i = utils.start_var_scope(i_var)
wml.variables[i_var] = first
local function loop_condition()
local sentinel = loop_lim.last
if loop_lim.step then
sentinel = sentinel + loop_lim.step
if loop_lim.step > 0 then
return wml.variables[i_var] < sentinel
else
return wml.variables[i_var] > sentinel
end
elseif loop_lim.last < first then
sentinel = sentinel - 1
return wml.variables[i_var] > sentinel
else
sentinel = sentinel + 1
return wml.variables[i_var] < sentinel
end
end
while loop_condition() do
for do_child in wml.child_range( cfg, "do" ) do
local action = utils.handle_event_commands(do_child, "loop")
if action == "break" then
utils.set_exiting("none")
goto exit
elseif action == "continue" then
utils.set_exiting("none")
break
elseif action ~= "none" then
goto exit
end
end
wml.variables[i_var] = wml.variables[i_var] + loop_lim.step
end
::exit::
utils.end_var_scope(i_var, save_i)
end
wml_actions["repeat"] = function(cfg)
if wml.child_count(cfg, "do") == 0 then
helper.wml_error "[repeat] does not contain any [do] tags"
end
local times = cfg.times or 1
for i = 1, times do
for do_child in wml.child_range( cfg, "do" ) do
local action = utils.handle_event_commands(do_child, "loop")
if action == "break" then
utils.set_exiting("none")
return
elseif action == "continue" then
utils.set_exiting("none")
break
elseif action ~= "none" then
return
end
end
end
end
function wml_actions.foreach(cfg)
if wml.child_count(cfg, "do") == 0 then
helper.wml_error "[foreach] does not contain any [do] tags"
end
local array_name = cfg.array or helper.wml_error "[foreach] missing required array= attribute"
local array = wml.array_access.get(array_name)
if #array == 0 then return end -- empty and scalars unwanted
local item_name = cfg.variable or "this_item"
local this_item = utils.start_var_scope(item_name) -- if this_item is already set
local i_name = cfg.index_var or "i"
local i = utils.start_var_scope(i_name) -- if i is already set
local array_length = wml.variables[array_name .. ".length"]
for index, value in ipairs(array) do
-- Some protection against external modification
-- It's not perfect, though - it'd be nice if *any* change could be detected
if array_length ~= wml.variables[array_name .. ".length"] then
helper.wml_error("WML array length changed during [foreach] iteration")
end
wml.variables[item_name] = value
-- set index variable
wml.variables[i_name] = index-1 -- here -1, because of WML array
-- perform actions
for do_child in wml.child_range(cfg, "do") do
local action = utils.handle_event_commands(do_child, "loop")
if action == "break" then
utils.set_exiting("none")
goto exit
elseif action == "continue" then
utils.set_exiting("none")
break
elseif action ~= "none" then
goto exit
end
end
-- set back the content, in case the author made some modifications
if not cfg.readonly then
array[index] = wml.variables[item_name]
end
end
::exit::
-- house cleaning
utils.end_var_scope(item_name, this_item)
utils.end_var_scope(i_name, i)
--[[
This forces the readonly key to be taken literally.
If readonly=yes, then this line guarantees that the array
is unchanged after the [foreach] loop ends.
If readonly=no, then this line updates the array with any
changes the user has applied through the $this_item
variable (or whatever variable was given in item_var).
Note that altering the array via indexing (with the index_var)
is not supported; any such changes will be reverted by this line.
]]
wml.array_access.set(array_name, array)
end
function wml_actions.switch(cfg)
local var_value = wml.variables[cfg.variable]
local found = false
-- Execute all the [case]s where the value matches.
for v in wml.child_range(cfg, "case") do
for w in utils.split(v.value) do
if w == tostring(var_value) then
local action = utils.handle_event_commands(v, "switch")
found = true
if action ~= "none" then goto exit end
break
end
end
end
::exit::
-- Otherwise execute [else] statements.
if not found then
for v in wml.child_range(cfg, "else") do
local action = utils.handle_event_commands(v, "switch")
if action ~= "none" then break end
end
end
end