Support [foreach]array=this_item, nested loops using the default name
Since a0ee38a49
, the inner this_item was still in scope when the inner loop
writes changed data back to the outer loop's variable, which meant that
changes were silently ignored.
A lot of the changes in wml-flow.lua are just indentation because of the
extra block.
This commit is contained in:
parent
49ebbb9709
commit
0a0263e54f
3 changed files with 92 additions and 27 deletions
|
@ -179,36 +179,51 @@ function wml_actions.foreach(cfg)
|
|||
local array = wml.array_variables[array_name]
|
||||
if #array == 0 then return end -- empty and scalars unwanted
|
||||
local item_name = cfg.variable or "this_item"
|
||||
local this_item <close> = utils.scoped_var(item_name) -- if this_item is already set
|
||||
local i_name = cfg.index_var or "i"
|
||||
local i <close> = utils.scoped_var(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
|
||||
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
|
||||
-- Some protection against unsupported modification of the array while iterating. Changes to
|
||||
-- via the WML variable named array_name will be ignored during the loop, and then overwritten
|
||||
-- by the contents of "array" afterwards. The check_for_modifications is just trying to add an
|
||||
-- error message instead of silently ignoring those changes.
|
||||
local array_length = wml.variables[array_name .. ".length"]
|
||||
local check_for_modifications = true
|
||||
if array_name:find("^" .. item_name) then
|
||||
-- Disable the check for WML such as [for]array=this_item or [for]array=this_item.abilities,
|
||||
-- where the current item is shadowing the variable of the same name outside the loop.
|
||||
check_for_modifications = false
|
||||
end
|
||||
|
||||
do
|
||||
local this_item <close> = utils.scoped_var(item_name)
|
||||
local i <close> = utils.scoped_var(i_name)
|
||||
|
||||
for index, value in ipairs(array) do
|
||||
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
|
||||
|
||||
-- Update the copy which will eventually be written back to the
|
||||
-- array, in case the author made some modifications.
|
||||
if not cfg.readonly then
|
||||
array[index] = wml.variables[item_name]
|
||||
end
|
||||
|
||||
if check_for_modifications and array_length ~= wml.variables[array_name .. ".length"] then
|
||||
wml.error("WML array length changed during [foreach] iteration")
|
||||
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::
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# wmllint: no translatables
|
||||
|
||||
#####
|
||||
# API(s) being tested: [foreach],[foreach]array=this_item
|
||||
#
|
||||
# The [foreach] loop implements local-scoping for the variables this_item and i.
|
||||
# This tests the behavior when looping over an array that is itself called this_item.
|
||||
##
|
||||
# Actions:
|
||||
# Simulate storing a unit with heals+4 to a variable.
|
||||
# Boost the strength of the healing ability.
|
||||
##
|
||||
# Expected end state:
|
||||
# The unit's healing ability is heals+8.
|
||||
#####
|
||||
{GENERIC_UNIT_TEST foreach_mutate_nested (
|
||||
[event]
|
||||
name=start
|
||||
|
||||
# A small subset of the result of storing a unit to a variable
|
||||
{VARIABLE u.abilities[0].regenerate.value 4}
|
||||
{VARIABLE u.abilities[1].heals.value 4}
|
||||
|
||||
# This makes the length of the inner this_item different to that of
|
||||
# the outer this_item, to test that the sanity check for external
|
||||
# modification isn't triggered by the matching names.
|
||||
{VARIABLE u.abilities[1].heals.test_attribute 4}
|
||||
|
||||
[foreach]
|
||||
array=u.abilities
|
||||
[do]
|
||||
[foreach]
|
||||
array=this_item.heals
|
||||
[do]
|
||||
[set_variable]
|
||||
name=this_item.value
|
||||
value=8
|
||||
[/set_variable]
|
||||
[/do]
|
||||
[/foreach]
|
||||
[/do]
|
||||
[/foreach]
|
||||
|
||||
{ASSERT ({VARIABLE_CONDITIONAL u.abilities[0].regenerate.value equals 4})}
|
||||
{ASSERT ({VARIABLE_CONDITIONAL u.abilities[1].heals.value equals 8})}
|
||||
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
|
@ -401,6 +401,7 @@
|
|||
0 for_end2_step2
|
||||
0 for_end-2
|
||||
0 for_end-2_step-2
|
||||
0 foreach_mutate_nested
|
||||
# AI Config Parsing tests
|
||||
0 test_basic_simplified_aspect
|
||||
0 test_basic_abbreviated_aspect
|
||||
|
|
Loading…
Add table
Reference in a new issue