jssm
Version:
A Javascript finite state machine (FSM) with a terse DSL and a simple API. Most FSMs are one-liners. Fast, easy, powerful, well tested, typed with TypeScript, and visualizations. MIT License.
676 lines (675 loc) • 24.5 kB
TypeScript
declare type StateType = string;
import { JssmGenericState, JssmGenericConfig, JssmTransition, JssmTransitionList, // JssmTransitionRule,
JssmMachineInternalState, JssmParseTree, JssmStateDeclaration, JssmArrow, JssmArrowDirection, JssmArrowKind, JssmLayout, FslDirection, FslTheme, HookDescription, HookHandler, HookContext, HookResult, HookComplexResult } from './jssm_types';
import { seq, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key } from './jssm_util';
import { shapes, gviz_shapes, named_colors } from './jssm_constants';
import { version } from './version';
/*********
*
* Return the direction of an arrow - `right`, `left`, or `both`.
*
* ```typescript
* import { arrow_direction } from 'jssm';
*
* arrow_direction('->'); // 'right'
* arrow_direction('<~=>'); // 'both'
* ```
*
* @param arrow The arrow to be evaluated
*
*/
declare function arrow_direction(arrow: JssmArrow): JssmArrowDirection;
/*********
*
* Return the direction of an arrow - `right`, `left`, or `both`.
*
* ```typescript
* import { arrow_left_kind } from 'jssm';
*
* arrow_left_kind('<-'); // 'legal'
* arrow_left_kind('<='); // 'main'
* arrow_left_kind('<~'); // 'forced'
* arrow_left_kind('<->'); // 'legal'
* arrow_left_kind('->'); // 'none'
* ```
*
* @param arrow The arrow to be evaluated
*
*/
declare function arrow_left_kind(arrow: JssmArrow): JssmArrowKind;
/*********
*
* Return the direction of an arrow - `right`, `left`, or `both`.
*
* ```typescript
* import { arrow_left_kind } from 'jssm';
*
* arrow_left_kind('->'); // 'legal'
* arrow_left_kind('=>'); // 'main'
* arrow_left_kind('~>'); // 'forced'
* arrow_left_kind('<->'); // 'legal'
* arrow_left_kind('<-'); // 'none'
* ```
*
* @param arrow The arrow to be evaluated
*
*/
declare function arrow_right_kind(arrow: JssmArrow): JssmArrowKind;
/*********
*
* This method wraps the parser call that comes from the peg grammar,
* {@link parse}. Generally neither this nor that should be used directly
* unless you mean to develop plugins or extensions for the machine.
*
* Parses the intermediate representation of a compiled string down to a
* machine configuration object. If you're using this (probably don't,) you're
* probably also using {@link compile} and {@link Machine.constructor}.
*
* ```typescript
* import { parse, compile, Machine } from 'jssm';
*
* const intermediate = wrap_parse('a -> b;', {});
* // [ {key:'transition', from:'a', se:{kind:'->',to:'b'}} ]
*
* const cfg = compile(intermediate);
* // { start_states:['a'], transitions: [{ from:'a', to:'b', kind:'legal', forced_only:false, main_path:false }] }
*
* const machine = new Machine(cfg);
* // Machine { _instance_name: undefined, _state: 'a', ...
* ```
*
* This method is mostly for plugin and intermediate tool authors, or people
* who need to work with the machine's intermediate representation.
*
* # Hey!
*
* Most people looking at this want either the `sm` operator or method `from`,
* which perform all the steps in the chain. The library's author mostly uses
* operator `sm`, and mostly falls back to `.from` when needing to parse
* strings dynamically instead of from template literals.
*
* Operator {@link sm}:
*
* ```typescript
* import { sm } from 'jssm';
*
* const switch = sm`on <=> off;`;
* ```
*
* Method {@link from}:
*
* ```typescript
* import * as jssm from 'jssm';
*
* const toggle = jssm.from('up <=> down;');
* ```
*
* `wrap_parse` itself is an internal convenience method for alting out an
* object as the options call. Not generally meant for external use.
*
* @param input The FSL code to be evaluated
*
* @param options Things to control about the instance
*
*/
declare function wrap_parse(input: string, options?: Object): any;
/*********
*
* Compile a machine's JSON intermediate representation to a config object. If
* you're using this (probably don't,) you're probably also using
* {@link parse} to get the IR, and the object constructor
* {@link Machine.construct} to turn the config object into a workable machine.
*
* ```typescript
* import { parse, compile, Machine } from 'jssm';
*
* const intermediate = parse('a -> b;');
* // [ {key:'transition', from:'a', se:{kind:'->',to:'b'}} ]
*
* const cfg = compile(intermediate);
* // { start_states:['a'], transitions: [{ from:'a', to:'b', kind:'legal', forced_only:false, main_path:false }] }
*
* const machine = new Machine(cfg);
* // Machine { _instance_name: undefined, _state: 'a', ...
* ```
*
* This method is mostly for plugin and intermediate tool authors, or people
* who need to work with the machine's intermediate representation.
*
* # Hey!
*
* Most people looking at this want either the `sm` operator or method `from`,
* which perform all the steps in the chain. The library's author mostly uses
* operator `sm`, and mostly falls back to `.from` when needing to parse
* strings dynamically instead of from template literals.
*
* Operator {@link sm}:
*
* ```typescript
* import { sm } from 'jssm';
*
* const switch = sm`on <=> off;`;
* ```
*
* Method {@link from}:
*
* ```typescript
* import * as jssm from 'jssm';
*
* const toggle = jssm.from('up <=> down;');
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param tree The parse tree to be boiled down into a machine config
*
*/
declare function compile<mDT>(tree: JssmParseTree): JssmGenericConfig<mDT>;
/*********
*
* An internal convenience wrapper for parsing then compiling a machine string.
* Not generally meant for external use. Please see {@link compile} or
* {@link sm}.
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param plan The FSL code to be evaluated and built into a machine config
*
*/
declare function make<mDT>(plan: string): JssmGenericConfig<mDT>;
/*********
*
* An internal method meant to take a series of declarations and fold them into
* a single multi-faceted declaration, in the process of building a state. Not
* generally meant for external use.
*
* @internal
*
*/
declare function transfer_state_properties(state_decl: JssmStateDeclaration): JssmStateDeclaration;
declare class Machine<mDT> {
_state: StateType;
_states: Map<StateType, JssmGenericState>;
_edges: Array<JssmTransition<mDT>>;
_edge_map: Map<StateType, Map<StateType, number>>;
_named_transitions: Map<StateType, number>;
_actions: Map<StateType, Map<StateType, number>>;
_reverse_actions: Map<StateType, Map<StateType, number>>;
_reverse_action_targets: Map<StateType, Map<StateType, number>>;
_machine_author?: Array<string>;
_machine_comment?: string;
_machine_contributor?: Array<string>;
_machine_definition?: string;
_machine_language?: string;
_machine_license?: string;
_machine_name?: string;
_machine_version?: string;
_fsl_version?: string;
_raw_state_declaration?: Array<Object>;
_state_declarations: Map<StateType, JssmStateDeclaration>;
_instance_name: string;
_data?: mDT;
_graph_layout: JssmLayout;
_dot_preamble: string;
_arrange_declaration: Array<Array<StateType>>;
_arrange_start_declaration: Array<Array<StateType>>;
_arrange_end_declaration: Array<Array<StateType>>;
_theme: FslTheme;
_flow: FslDirection;
_has_hooks: boolean;
_has_basic_hooks: boolean;
_has_named_hooks: boolean;
_has_entry_hooks: boolean;
_has_exit_hooks: boolean;
_has_global_action_hooks: boolean;
_has_transition_hooks: boolean;
_hooks: Map<string, HookHandler<mDT>>;
_named_hooks: Map<string, HookHandler<mDT>>;
_entry_hooks: Map<string, HookHandler<mDT>>;
_exit_hooks: Map<string, HookHandler<mDT>>;
_global_action_hooks: Map<string, HookHandler<mDT>>;
_any_action_hook: HookHandler<mDT> | undefined;
_standard_transition_hook: HookHandler<mDT> | undefined;
_main_transition_hook: HookHandler<mDT> | undefined;
_forced_transition_hook: HookHandler<mDT> | undefined;
_any_transition_hook: HookHandler<mDT> | undefined;
constructor({ start_states, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout, instance_name, data }: JssmGenericConfig<mDT>);
/********
*
* Internal method for fabricating states. Not meant for external use.
*
* @internal
*
*/
_new_state(state_config: JssmGenericState): StateType;
/*********
*
* Get the current state of a machine.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const switch = jssm.from('on <=> off;');
* console.log( switch.state() ); // 'on'
*
* switch.transition('off');
* console.log( switch.state() ); // 'off'
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
*/
state(): StateType;
/*********
*
* Get the current data of a machine.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const switch = jssm.from('on <=> off;', {data: 1});
* console.log( switch.data() ); // 1
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
*/
data(): mDT;
/********
*
* Check whether a given state is final (either has no exits or is marked
* `complete`.)
*
* ```typescript
* import { sm, state_is_final } from 'jssm';
*
* const final_test = sm`first -> second;`;
*
* console.log( final_test.state_is_final('first') ); // false
* console.log( final_test.state_is_final('second') ); // true
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The name of the state to check for finality
*
*/
state_is_final(whichState: StateType): boolean;
/********
*
* Check whether the current state is final (either has no exits or is marked
* `complete`.)
*
* ```typescript
* import { sm, state_is_final } from 'jssm';
*
* const final_test = sm`first -> second;`;
*
* console.log( final_test.is_final() ); // false
* state.transition('second');
* console.log( final_test.is_final() ); // true
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
*/
is_final(): boolean;
graph_layout(): string;
dot_preamble(): string;
machine_author(): Array<string>;
machine_comment(): string;
machine_contributor(): Array<string>;
machine_definition(): string;
machine_language(): string;
machine_license(): string;
machine_name(): string;
machine_version(): string;
raw_state_declarations(): Array<Object>;
state_declaration(which: StateType): JssmStateDeclaration;
state_declarations(): Map<StateType, JssmStateDeclaration>;
fsl_version(): string;
machine_state(): JssmMachineInternalState<mDT>;
/*********
*
* List all the states known by the machine. Please note that the order of
* these states is not guaranteed.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const switch = jssm.from('on <=> off;');
* console.log( switch.states() ); // ['on', 'off']
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
*/
states(): Array<StateType>;
state_for(whichState: StateType): JssmGenericState;
/*********
*
* Check whether the machine knows a given state.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const switch = jssm.from('on <=> off;');
*
* console.log( switch.has_state('off') ); // true
* console.log( switch.has_state('dance') ); // false
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state to be checked for extance
*
*/
has_state(whichState: StateType): boolean;
/*********
*
* Lists all edges of a machine.
*
* ```typescript
* import { sm } from 'jssm';
*
* const lswitch = sm`on 'toggle' <=> 'toggle' off;`;
*
* lswitch.list_edges();
* [
* {
* from: 'on',
* to: 'off',
* kind: 'main',
* forced_only: false,
* main_path: true,
* action: 'toggle'
* },
* {
* from: 'off',
* to: 'on',
* kind: 'main',
* forced_only: false,
* main_path: true,
* action: 'toggle'
* }
* ]
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
*/
list_edges(): Array<JssmTransition<mDT>>;
list_named_transitions(): Map<StateType, number>;
list_actions(): Array<StateType>;
theme(): FslTheme;
flow(): FslDirection;
get_transition_by_state_names(from: StateType, to: StateType): number;
lookup_transition_for(from: StateType, to: StateType): JssmTransition<mDT>;
/********
*
* List all transitions attached to the current state, sorted by entrance and
* exit. The order of each sublist is not defined. A node could appear in
* both lists.
*
* ```typescript
* import { sm } from 'jssm';
*
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.list_transitions(); // { entrances: [ 'yellow', 'off' ], exits: [ 'green', 'off' ] }
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state whose transitions to have listed
*
*/
list_transitions(whichState?: StateType): JssmTransitionList;
/********
*
* List all entrances attached to the current state. Please note that the
* order of the list is not defined.
*
* ```typescript
* import { sm } from 'jssm';
*
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.list_entrances(); // [ 'yellow', 'off' ]
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state whose entrances to have listed
*
*/
list_entrances(whichState?: StateType): Array<StateType>;
/********
*
* List all exits attached to the current state. Please note that the order
* of the list is not defined.
*
* ```typescript
* import { sm } from 'jssm';
*
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.list_exits(); // [ 'green', 'off' ]
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state whose exits to have listed
*
*/
list_exits(whichState?: StateType): Array<StateType>;
probable_exits_for(whichState: StateType): Array<JssmTransition<mDT>>;
probabilistic_transition(): boolean;
probabilistic_walk(n: number): Array<StateType>;
probabilistic_histo_walk(n: number): Map<StateType, number>;
/********
*
* List all actions available from this state. Please note that the order of
* the actions is not guaranteed.
*
* ```typescript
* import { sm } from 'jssm';
*
* const machine = sm`
* red 'next' -> green 'next' -> yellow 'next' -> red;
* [red yellow green] 'shutdown' ~> off 'start' -> red;
* `;
*
* console.log( machine.state() ); // logs 'red'
* console.log( machine.actions() ); // logs ['next', 'shutdown']
*
* machine.action('next'); // true
* console.log( machine.state() ); // logs 'green'
* console.log( machine.actions() ); // logs ['next', 'shutdown']
*
* machine.action('shutdown'); // true
* console.log( machine.state() ); // logs 'off'
* console.log( machine.actions() ); // logs ['start']
*
* machine.action('start'); // true
* console.log( machine.state() ); // logs 'red'
* console.log( machine.actions() ); // logs ['next', 'shutdown']
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The state whose actions to have listed
*
*/
actions(whichState?: StateType): Array<StateType>;
/********
*
* List all states that have a specific action attached. Please note that
* the order of the states is not guaranteed.
*
* ```typescript
* import { sm } from 'jssm';
*
* const machine = sm`
* red 'next' -> green 'next' -> yellow 'next' -> red;
* [red yellow green] 'shutdown' ~> off 'start' -> red;
* `;
*
* console.log( machine.list_states_having_action('next') ); // ['red', 'green', 'yellow']
* console.log( machine.list_states_having_action('start') ); // ['off']
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param whichState The action to be checked for associated states
*
*/
list_states_having_action(whichState: StateType): Array<StateType>;
list_exit_actions(whichState?: StateType): Array<StateType>;
probable_action_exits(whichState?: StateType): Array<any>;
is_unenterable(whichState: StateType): boolean;
has_unenterables(): boolean;
is_terminal(): boolean;
state_is_terminal(whichState: StateType): boolean;
has_terminals(): boolean;
is_complete(): boolean;
state_is_complete(whichState: StateType): boolean;
has_completes(): boolean;
set_hook(HookDesc: HookDescription<mDT>): void;
hook(from: string, to: string, handler: HookHandler<mDT>): Machine<mDT>;
hook_action(from: string, to: string, action: string, handler: HookHandler<mDT>): Machine<mDT>;
hook_global_action(action: string, handler: HookHandler<mDT>): Machine<mDT>;
hook_any_action(handler: HookHandler<mDT>): Machine<mDT>;
hook_standard_transition(handler: HookHandler<mDT>): Machine<mDT>;
hook_main_transition(handler: HookHandler<mDT>): Machine<mDT>;
hook_forced_transition(handler: HookHandler<mDT>): Machine<mDT>;
hook_any_transition(handler: HookHandler<mDT>): Machine<mDT>;
hook_entry(to: string, handler: HookHandler<mDT>): Machine<mDT>;
hook_exit(from: string, handler: HookHandler<mDT>): Machine<mDT>;
edges_between(from: string, to: string): JssmTransition<mDT>[];
transition_impl(newStateOrAction: StateType, newData: mDT | undefined, wasForced: boolean, wasAction: boolean): boolean;
/********
*
* Instruct the machine to complete an action.
*
* ```typescript
* const light = sm`red 'next' -> green 'next' -> yellow 'next' -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.action('next'); // true
* light.state(); // 'green'
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param actionName The action to engage
*
* @param newData The data change to insert during the action
*
*/
action(actionName: StateType, newData?: mDT): boolean;
/********
*
* Instruct the machine to complete a transition.
*
* ```typescript
* const light = sm`red -> green -> yellow -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.transition('green'); // true
* light.state(); // 'green'
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param newState The state to switch to
*
* @param newData The data change to insert during the transition
*
*/
transition(newState: StateType, newData?: mDT): boolean;
/********
*
* Instruct the machine to complete a forced transition (which will reject if
* called with a normal {@link transition} call.)
*
* ```typescript
* const light = sm`red -> green -> yellow -> red; [red yellow green] 'shutdown' ~> off 'start' -> red;`;
*
* light.state(); // 'red'
* light.transition('off'); // false
* light.state(); // 'red'
* light.force_transition('off'); // true
* light.state(); // 'off'
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param newState The state to switch to
*
* @param newData The data change to insert during the transition
*
*/
force_transition(newState: StateType, newData?: mDT): boolean;
current_action_for(action: StateType): number;
current_action_edge_for(action: StateType): JssmTransition<mDT>;
valid_action(action: StateType, _newData?: mDT): boolean;
valid_transition(newState: StateType, _newData?: mDT): boolean;
valid_force_transition(newState: StateType, _newData?: mDT): boolean;
instance_name(): string | undefined;
sm(template_strings: TemplateStringsArray, ...remainder: any[]): Machine<mDT>;
}
/*********
*
* Create a state machine from a template string. This is one of the two main
* paths for working with JSSM, alongside {@link from}.
*
* Use this method when you want to work directly and conveniently with a
* constant template expression. Use `.from` when you want to pull from
* dynamic strings.
*
*
* ```typescript
* import * as jssm from 'jssm';
*
* const switch = jssm.from('on <=> off;');
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param template_strings The assembled code
*
* @param remainder The mechanic for template argument insertion
*
*/
declare function sm<mDT>(template_strings: TemplateStringsArray, ...remainder: any[]): Machine<mDT>;
/*********
*
* Create a state machine from an implementation string. This is one of the
* two main paths for working with JSSM, alongside {@link sm}.
*
* Use this method when you want to conveniently pull a state machine from a
* string dynamically. Use operator `sm` when you just want to work with a
* template expression.
*
* ```typescript
* import * as jssm from 'jssm';
*
* const switch = jssm.from('on <=> off;');
* ```
*
* @typeparam mDT The type of the machine data member; usually omitted
*
* @param MachineAsString The FSL code to evaluate
*
* @param ExtraConstructorFields Extra non-code configuration to pass at creation time
*
*/
declare function from<mDT>(MachineAsString: string, ExtraConstructorFields?: Partial<JssmGenericConfig<mDT>> | undefined): Machine<mDT>;
declare function is_hook_complex_result<mDT>(hr: unknown): hr is HookComplexResult<mDT>;
declare function is_hook_rejection<mDT>(hr: HookResult<mDT>): boolean;
declare function abstract_hook_step<mDT>(maybe_hook: HookHandler<mDT> | undefined, hook_args: HookContext<mDT>): HookComplexResult<mDT>;
export { version, transfer_state_properties, Machine, make, wrap_parse as parse, compile, sm, from, arrow_direction, arrow_left_kind, arrow_right_kind, seq, weighted_rand_select, histograph, weighted_sample_select, weighted_histo_key, shapes, gviz_shapes, named_colors, is_hook_rejection, is_hook_complex_result, abstract_hook_step };