UNPKG

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
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 };