--- Helper module for specifying finite state machines (FSMs) with. Provides a DSL. -- @module lqc.fsm -- @alias fsm local algorithm = require 'lqc.fsm.algorithm' local state = require 'lqc.fsm.state' local lqc = require 'lqc.quickcheck' --- Adds a stop state to the list of states. -- This is a special predefined state that will stop the FSM from generating -- more state transitions. -- @param state_list List of states in the FSM (not including stop state) -- @return the updated state list (variable modified in place). local function add_stop_state(state_list) table.insert(state_list, state 'stop' { precondition = function() return true end, -- always succeeds next_state = function() return nil end, -- not used postcondition = function() return true end, -- always succeeds }) return state_list end --- Checks if an object is callable (function or functable): -- @param obj Object to be checked if it is callable -- @return true if it is callable; otherwise false local function is_callable(obj) local type_obj = type(obj) return type_obj == 'function' or type_obj == 'table' end --- Checks if the FSM table contains a valid specification of a state machine -- @param fsm_table Table containing FSM information/description -- @return nil; raises an error message if specification is not valid local function check_valid_fsm_spec(fsm_table) if not is_callable(fsm_table.commands) then error 'Need to provide list of commands to FSM!' end if not is_callable(fsm_table.initial_state) then error 'Need to provide initial state function to FSM!' end local states = fsm_table.states if type(states) ~= 'table' then error 'Need to provide a table of possible states of the FSM!' end -- States are already checked in state.lua end local function default_cleanup() end local function default_when_fail() end --- Constructs a new FSM -- @param description text description of the FSM -- @param fsm_table table containing FSM info -- @return FSM object local function new(description, fsm_table) local FSM = {} function FSM.check(_) return algorithm.check(description, fsm_table) end return FSM end --- Creates a new FSM and inserts it into the list of properties. -- @param descr Text description of the FSM -- @param fsm_info_table Table containing information of the FSM local function fsm(descr, fsm_info_table) local function fsm_func(fsm_table) fsm_table.states = add_stop_state(fsm_table.states) fsm_table.cleanup = fsm_table.cleanup or default_cleanup fsm_table.when_fail = fsm_table.when_fail or default_when_fail fsm_table.numtests = fsm_table.numtests or lqc.numtests fsm_table.numshrinks = fsm_table.numshrinks or lqc.numshrinks check_valid_fsm_spec(fsm_table) local new_fsm = new(descr, fsm_table) table.insert(lqc.properties, new_fsm) end if fsm_info_table then -- Called normally (most likely from Moonscript) fsm_func(fsm_info_table) return function() end end -- Called with DSL syntax return fsm_func end return fsm