/**
* @author Wolfgang Kowarschick <kowa@hs-augsburg.de>
* @copyright 2017 - 2018
* @license CC-BY-NC-SA-4.0
*/
import EventDispatcher from "../observer/EventDispatcher.js";
const C_EMPTY = {};
/**
* Implements a class the objects of which are finite automatons
* (state machines).
*
* @class
*/
class Automaton extends EventDispatcher
{ /**
* A finite automaton is in exactly one of a finite number of states
* at any given time. Initially it is in the initial state.
* <p>
* Whenever an action is performed, it changes its state due to
* the transition table given. The new state depends on the
* current state and the action performed.
* <p>
* On exiting a state the automaton dispatches the exit event of that
* state (if that event exists). Then it dispatches the do event of the
* current transition (if that event exits). On entering a state,
* it dispatches the entry event and then the do event of that state
* (if those events exist). If a transition does not lead to a new state,
* i.e., if the automaton remains in the current state, neither the exit
* event nor the entry event but only the do event of the transition
* an the do event of that state are dispatched.
* <p>
* In the transition table there can be noted a default action
* for every state. Whenever the automaton is in a specific state
* and there is no entry found for the current action, the default
* action is performed instead. If no default action is found, too,
* the automaton remains in the current state and an error event
* is dispatched (if it exists).
*
* @param {{initialState: string,
* defaultAction: ?string,
* errorEvent: ?string,
* states: ?Object.<string,
* {entry: ?(string|function),
* do: ?(string|function),
* exit: ?(string|function)
* }
* >,
* transitions: Object.<string,
* Object.<string,
* string
* |
* {state: string,
* do: ?(string|function)
* }
* >
* >
* }
* } automaton
*
* @param {string} automaton.initialState
* The initial state of the automaton.
*
* @param {?string} automaton.defaultAction
* An action that is performed, if the action to be performed
* does not exists.
*
* @param {?string} automaton.errorEvent
* This event is dispatched, if no transition is found (neither
* for the action passed to the automaton nor for the default action).
*
* @param {Object.<string,
* {entry: ?(string|function),
* do: ?(string|function),
* exit: ?(string|function)
* }
* >
* } [automaton.states = {}]
* An object that defines the entry, do, and exit actions (functions)
* or events (strings) of states of the automaton. Those actions are
* optional. If a state has no dedicated actions, it may or may not
* be stated here.
*
* @param {Object.<string,
* Object.<string,
* string
* |
* {state: string,
* do: ?(string|function)
* }
* >
* >
* } [automaton.transitions = {}]
* The transitions of the automaton. For each possible state of the
* automaton an object containing all possible actions that can be
* performed in that state is to be declared. For each action
* the new state has to be declared. Additionally an do action
* (Function) or event (string) may may be added.
*
* @param {Object} automaton.transitions.*
* The states of the automaton.
*
* @param {Object} automaton.transitions.*.*
* The action associated with the corresponding state.
*
* @param {string} automaton.transitions.*.*.state
* The new state the automaton switches into after the action
* has been performed.
*
* @param {?string} automaton.transitions.*.*.do
* An optional reaction (do action) of the automaton when an
* action is passed to it (while it is in the corresponding state).
*/
constructor({initialState: p_initial_state,
states: p_states = C_EMPTY,
transitions: p_transitions = C_EMPTY,
defaultAction: p_default_action = null,
errorEvent: p_error_event = null
})
{ super();
this.v_initial_state_name = p_initial_state;
this.v_states = p_states;
this.v_transitions = p_transitions;
this.v_default_action = p_default_action;
this.v_error_event = p_error_event;
this.reset();
}
/** @private */
m_dispatch_event(p_type, p_action = null)
{ if (typeof p_type === 'string')
{ this.dispatchEvent(p_type,
{action: p_action,
previousState: this.v_state_name_previous,
state: this.v_state
}
)
}
else if (typeof p_type === 'function')
{ p_type({action: p_action,
previousState: this.v_state_name_previous,
state: this.v_state
}
)
}
}
/**
* Resets the automaton to its initial state;
*/
reset()
{ this.v_state_name = this.v_initial_state_name;
this.v_state_name_previous = null;
this.v_state = this.v_states[this.v_state_name] || C_EMPTY;
this.v_transitions_state = this.v_transitions[this.v_state_name] || C_EMPTY;
this.v_last_action = null;
this.m_dispatch_event(this.v_state.entry);
this.m_dispatch_event(this.v_state.do);
}
/**
* Performs an action on the automation. It reacts by changing its state
* an dispatching appropriate events.
*
* @param {string} p_action
*/
action(p_action)
{ let
l_transition = this.v_transitions_state[p_action],
l_state_name_previous,
l_state_name,
l_state;
if (l_transition == null)
{ l_transition = this.v_transitions_state[this.v_default_action]; }
if (l_transition == null)
{ if (this.v_error_event != null)
{ this.m_dispatch_event(this.v_error_event, p_action); }
}
else
{ this.v_last_action = p_action;
l_state_name_previous = this.v_state_name_previous = this.v_state_name;
let l_do_event = null;
if (typeof l_transition === 'string')
{ l_state_name = this.v_state_name = l_transition; }
else
{ l_state_name = this.v_state_name = l_transition.state;
l_do_event = l_transition.do;
}
if (l_state_name !== l_state_name_previous)
{ this.m_dispatch_event(this.v_state.exit, p_action); }
l_state = this.v_state = this.v_states[this.v_state_name] || C_EMPTY;
this.v_transitions_state = this.v_transitions[this.v_state_name] || C_EMPTY;
this.m_dispatch_event(l_do_event, p_action);
if (l_state_name !== l_state_name_previous)
{ this.m_dispatch_event(l_state.entry, p_action); }
this.m_dispatch_event(l_state.do, p_action);
}
}
/**
* @returns {string} The current state of the automaton;
*/
get state()
{ return this.v_state_name; }
/**
* @returns {string} The previous state of the automaton;
*/
get previousState()
{ return this.v_state_name_previous; }
/**
* @returns {string} The last action that was performed by the automaton;
*/
get lastAction()
{ return this.v_last_action; }
}
export {Automaton};
export default Automaton;