--- Module for representing a command internally in a 'declarative' way -- @module lqc.fsm.command -- @alias command local Gen = require 'lqc.generator' local random = require 'lqc.random' local deep_copy = require 'lqc.helpers.deep_copy' --- Returns a string representation of the command. -- @param cmd Command to transform into a string -- @return string representation of the command local function stringify(cmd) local result = {'{ call, ', cmd.state_name} local size_result = #result for i = 1, #cmd.args do -- TODO allow printing of table etc.. result[i + size_result] = ', ' .. cmd.args[i] end result[#result + 1] = ' }' return table.concat(result) end --- Creates a function that picks a random value for each of the generators -- specified in the argument list. -- @param state_name Name of the state this command is associated with -- @param command_func Function to be called when this command is selected -- @param args_generators List of generators that generate the arguments for command_func -- @return table with keys { state_name, func, args } local function pick(state_name, command_func, args_generators) local function do_pick(num_tests) local args = {} for i = 1, #args_generators do args[i] = args_generators[i]:pick(num_tests) end return { state_name = state_name, func = command_func, args = args, to_string = stringify, } end return do_pick end --- Does the actual shrinking of the args -- Randomly picks 1 of the arguments and shrinks it, rest stays the same -- @param prev_args Previously generated args for the command -- @param args_generators Generators used to generate and shrink the args -- @return shrunk down argument list local function shrink_args(prev_args, args_generators) local idx = random.between(1, #prev_args) local shrunk_arg = args_generators[idx]:shrink(prev_args[idx]) local args_copy = deep_copy(prev_args) args_copy[idx] = shrunk_arg return args_copy end --- Shrinks the command to a simpler form. -- Only args are shrunk, state_name and func are unmodified. -- @param state_name Name of the state this command is associated with -- @param command_func Function to be called when this command is selected -- @param args_generators Generators used to generate and shrink the args -- @return function that does the actual shrinking for this command local function shrink(state_name, command_func, args_generators) local function do_shrink(previous) if #previous.args == 0 then return previous end return { state_name = state_name, func = command_func, args = shrink_args(previous.args, args_generators), to_string = stringify, } end return do_shrink end --- Creates a new command generator with a state_name, command function -- and a list of generators (args will be passed into the command_func in the FSM) -- @param state_name Name of the state this command is associated with -- @param command_func Function to be called when this command is selected -- @param args_generators Generators used to generate and shrink the args -- @return generator for randomly creating a command local function new(state_name, command_func, args_generators) local generator = Gen.new(pick(state_name, command_func, args_generators), shrink(state_name, command_func, args_generators)) generator.state_name = state_name return generator end local command = {} local command_mt = { __call = function(_, command_tbl) return new(command_tbl[1], command_tbl[2], command_tbl[3]) end, } command.stop = new('stop', function() end, {}) return setmetatable(command, command_mt)