Implement [break], [continue], [return] tags (they take no arguments)

This commit is contained in:
Celtic Minstrel 2015-09-17 17:46:41 -04:00
parent 907d527cc3
commit 3c329408ce
6 changed files with 199 additions and 12 deletions

View file

@ -275,9 +275,11 @@ function wml_actions.music(cfg)
wesnoth.set_music(cfg)
end
wml_actions.command = utils.handle_event_commands
function wml_actions.command(cfg)
utils.handle_event_commands(cfg, "plain")
end
-- since if and while are Lua keywords, we can't create functions with such names
-- 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
@ -288,7 +290,8 @@ wml_actions["if"] = function(cfg)
if wesnoth.eval_conditional(cfg) then -- evaluate [if] tag
for then_child in helper.child_range(cfg, "then") do
utils.handle_event_commands(then_child)
local action = utils.handle_event_commands(then_child, "conditional")
if action ~= "none" then break end
end
return -- stop after executing [then] tags
end
@ -296,15 +299,18 @@ wml_actions["if"] = function(cfg)
for elseif_child in helper.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 helper.child_range(elseif_child, "then") do
utils.handle_event_commands(then_tag)
local action = utils.handle_event_commands(then_tag, "conditional")
if action ~= "none" then goto exit end
end
return -- stop on first matched condition
end
end
::exit::
-- no matched condition, try the [else] tags
for else_child in helper.child_range(cfg, "else") do
utils.handle_event_commands(else_child)
local action = utils.handle_event_commands(else_child, "conditional")
if action ~= "none" then break end
end
end
@ -313,10 +319,32 @@ wml_actions["while"] = function( cfg )
for i = 1, 65536 do
if wesnoth.eval_conditional( cfg ) then
for do_child in helper.child_range( cfg, "do" ) do
utils.handle_event_commands( do_child )
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
else return end
end
::exit::
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
function wml_actions.switch(cfg)
@ -327,17 +355,20 @@ function wml_actions.switch(cfg)
for v in helper.child_range(cfg, "case") do
for w in utils.split(v.value) do
if w == tostring(var_value) then
utils.handle_event_commands(v)
local action = utils.handle_event_commands(v, "switch")
found = true
break
if action ~= "none" then goto exit end
break
end
end
end
::exit::
-- Otherwise execute [else] statements.
if not found then
for v in helper.child_range(cfg, "else") do
utils.handle_event_commands(v)
local action = utils.handle_event_commands(v, "switch")
if action ~= "none" then break end
end
end
end

View file

@ -71,11 +71,34 @@ function utils.optional_side_filter(cfg, key_name, filter_name)
return false
end
function utils.handle_event_commands(cfg)
local current_exit = "none"
local scope_stack = {
push = table.insert,
pop = table.remove,
}
--[[ Possible exit types:
- none - ordinary execution
- break - exiting a loop scope
- return - immediate termination (exit all scopes)
- continue - jumping to the end of a loop scope
]]
function utils.set_exiting(exit_type)
current_exit = exit_type
end
--[[ Possible scope types:
- plain - ordinary scope, no special features; eg [command] or [event]
- conditional - scope that's executing because of a condition, eg [then] or [else]
- switch - scope that's part of a switch statement, eg [case] or [else]
- loop - scope that's part of a loop, eg [do]
Currently, only "loop" has any special effects. ]]
function utils.handle_event_commands(cfg, scope_type)
-- The WML might be modifying the currently executed WML by mixing
-- [insert_tag] with [set_variables] and [clear_variable], so we
-- have to be careful not to get confused by tags vanishing during
-- the execution, hence the manual handling of [insert_tag].
scope_stack:push(scope_type)
local cmds = helper.shallow_literal(cfg)
for i = 1,#cmds do
local v = cmds[i]
@ -104,6 +127,7 @@ function utils.handle_event_commands(cfg)
local j = 0
repeat
cmd(arg)
if current_exit ~= "none" then break end
j = j + 1
if j >= wesnoth.get_variable(insert_from .. ".length") then break end
arg = wesnoth.tovconfig(wesnoth.get_variable(string.format("%s[%d]", insert_from, j)))
@ -112,9 +136,18 @@ function utils.handle_event_commands(cfg)
cmd(arg)
end
end
if current_exit ~= "none" then break end
end
scope_stack:pop()
if #scope_stack == 0 then
if current_exit == "continue" and scope_type ~= "loop" then
helper.wml_error("[continue] found outside a loop scope!")
end
current_exit = "none"
end
-- Apply music alterations once all the commands have been processed.
wesnoth.set_music()
return current_exit
end
-- Splits the string argument on commas, excepting those commas that occur

View file

@ -214,7 +214,8 @@ function wesnoth.wml_actions.message(cfg)
end
for i, cmd in ipairs(option_events[option_chosen]) do
utils.handle_event_commands(cmd)
local action = utils.handle_event_commands(cmd, "plain")
if action ~= "none" then break end
end
end
end

View file

@ -55,7 +55,8 @@ function wml_actions.object(cfg)
end
for cmd in helper.child_range(cfg, command_type) do
utils.handle_event_commands(cmd)
local action = utils.handle_event_commands(cmd, "conditional")
if action ~= "none" then break end
end
end

View file

@ -0,0 +1,114 @@
{GENERIC_UNIT_TEST check_interrupts_break (
[event]
name=start
{VARIABLE x 0}
[while]
[true][/true]
[do]
[if]
{VARIABLE_CONDITIONAL x greater_than 5}
[then]
[break][/break]
[/then]
[/if]
{VARIABLE_OP x add 1}
[/do]
[/while]
{RETURN ({VARIABLE_CONDITIONAL x equals 6})}
[/event]
)}
{GENERIC_UNIT_TEST check_interrupts_return (
[event]
name=start
{VARIABLE x 0}
[while]
[true][/true]
[do]
[if]
{VARIABLE_CONDITIONAL x greater_than 5}
[then]
[return][/return]
[/then]
[/if]
{VARIABLE_OP x add 1}
[/do]
[/while]
{RETURN ([false][/false])}
[/event]
[event]
name=start
{RETURN ({VARIABLE_CONDITIONAL x equals 6})}
[/event]
)}
{GENERIC_UNIT_TEST check_interrupts_continue (
[event]
name=start
{VARIABLE x 0}
[while]
{VARIABLE_CONDITIONAL x less_than 1}
[do]
{VARIABLE_OP x add 1}
[continue][/continue]
{RETURN ([false][/false])}
[/do]
[/while]
{RETURN ([true][/true])}
[/event]
)}
{GENERIC_UNIT_TEST check_interrupts_break_global (
[event]
name=start
[break][/break]
{RETURN ([false][/false])}
[/event]
[event]
name=start
{RETURN ([true][/true])}
[/event]
)}
{GENERIC_UNIT_TEST check_interrupts_continue_global (
[event]
name=start
[lua]
code=<<
local H = wesnoth.require "lua/helper.lua"
local A = H.set_wml_action_metatable{}
local function continue()
A.continue{}
end
-- Use pcall() to trap the WML error raised by continue in global scope
local err, res = pcall(continue)
if err then wesnoth.fire_event "success"
else wesnoth.fire_event "fail" end
>>
[/lua]
[/event]
[event]
name=success
{RETURN ([true][/true])}
[/event]
[event]
name=fail
{RETURN ([false][/false])}
[/event]
)}
{GENERIC_UNIT_TEST check_interrupts_return_nested (
[event]
name=start
[command]
[return][/return]
{RETURN ([false][/false])}
[/command]
{RETURN ([false][/false])}
[/event]
[event]
name=start
{RETURN ([true][/true])}
[/event]
)}

View file

@ -143,3 +143,10 @@
#
0 check_conditionals_1
0 check_conditionals_2
# Interrupt tag tests
0 check_interrupts_break
0 check_interrupts_return
0 check_interrupts_continue
0 check_interrupts_break_global
0 check_interrupts_return_nested
0 check_interrupts_continue_global