diff --git a/benchmark/results/general.chart.html b/benchmark/results/general.chart.html index 61ede812..178c8dd3 100644 --- a/benchmark/results/general.chart.html +++ b/benchmark/results/general.chart.html @@ -28,7 +28,7 @@
- +
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class Machine<mDT>

Type parameters

  • mDT

Hierarchy

  • Machine

Index

Constructors

Properties

Methods

Constructors

  • new Machine<mDT>(__namedParameters: JssmGenericConfig<mDT>): Machine<mDT>
  • Type parameters

    • mDT

    Parameters

    • __namedParameters: JssmGenericConfig<mDT>

    Returns Machine<mDT>

Properties

_actions: Map<string, Map<string, number>>
_any_action_hook: Function
_any_transition_hook: Function
_arrange_declaration: string[][]
_arrange_end_declaration: string[][]
_arrange_start_declaration: string[][]
_dot_preamble: string
_edge_map: Map<string, Map<string, number>>
_edges: JssmTransition<mDT>[]
_entry_hooks: Map<string, Function>
_exit_hooks: Map<string, Function>
_flow: FslDirection
_forced_transition_hook: Function
_fsl_version?: string
_global_action_hooks: Map<string, Function>
_graph_layout: JssmLayout
_has_basic_hooks: boolean
_has_entry_hooks: boolean
_has_exit_hooks: boolean
_has_global_action_hooks: boolean
_has_hooks: boolean
_has_named_hooks: boolean
_has_transition_hooks: boolean
_hooks: Map<string, Function>
_instance_name: string
_machine_author?: string[]
_machine_comment?: string
_machine_contributor?: string[]
_machine_definition?: string
_machine_language?: string
_machine_license?: string
_machine_name?: string
_machine_version?: string
_main_transition_hook: Function
_named_hooks: Map<string, Function>
_named_transitions: Map<string, number>
_raw_state_declaration?: Object[]
_reverse_action_targets: Map<string, Map<string, number>>
_reverse_actions: Map<string, Map<string, number>>
_standard_transition_hook: Function
_state: string
_state_declarations: Map<string, JssmStateDeclaration>
_states: Map<string, JssmGenericState>
_theme: FslTheme

Methods

  • _new_state(state_config: JssmGenericState): string
  • Parameters

    • state_config: JssmGenericState

    Returns string

  • action(actionName: string, newData?: mDT): boolean
  • Parameters

    • actionName: string
    • Optional newData: mDT

    Returns boolean

  • actions(whichState?: string): string[]
  • Parameters

    • whichState: string = ...

    Returns string[]

  • current_action_edge_for(action: string): JssmTransition<mDT>
  • Parameters

    • action: string

    Returns JssmTransition<mDT>

  • current_action_for(action: string): number
  • Parameters

    • action: string

    Returns number

  • dot_preamble(): string
  • edges_between(from: string, to: string): JssmTransition<mDT>[]
  • Parameters

    • from: string
    • to: string

    Returns JssmTransition<mDT>[]

  • flow(): FslDirection
  • force_transition(newState: string, newData?: mDT): boolean
  • Parameters

    • newState: string
    • Optional newData: mDT

    Returns boolean

  • fsl_version(): string
  • get_transition_by_state_names(from: string, to: string): number
  • Parameters

    • from: string
    • to: string

    Returns number

  • graph_layout(): string
  • has_completes(): boolean
  • has_state(whichState: string): boolean
  • Parameters

    • whichState: string

    Returns boolean

  • has_terminals(): boolean
  • has_unenterables(): boolean
  • hook(from: string, to: string, handler: Function): Machine<mDT>
  • hook_action(from: string, to: string, action: string, handler: Function): Machine<mDT>
  • Parameters

    • from: string
    • to: string
    • action: string
    • handler: Function

    Returns Machine<mDT>

  • hook_any_action(handler: Function): Machine<mDT>
  • hook_any_transition(handler: Function): Machine<mDT>
  • hook_entry(to: string, handler: Function): Machine<mDT>
  • hook_exit(from: string, handler: Function): Machine<mDT>
  • hook_forced_transition(handler: Function): Machine<mDT>
  • hook_global_action(action: string, handler: Function): Machine<mDT>
  • hook_main_transition(handler: Function): Machine<mDT>
  • hook_standard_transition(handler: Function): Machine<mDT>
  • instance_name(): string
  • is_complete(): boolean
  • is_final(): boolean
  • is_terminal(): boolean
  • is_unenterable(whichState: string): boolean
  • Parameters

    • whichState: string

    Returns boolean

  • list_actions(): string[]
  • list_edges(): JssmTransition<mDT>[]
  • list_entrances(whichState?: string): string[]
  • Parameters

    • whichState: string = ...

    Returns string[]

  • list_exit_actions(whichState?: string): string[]
  • Parameters

    • whichState: string = ...

    Returns string[]

  • list_exits(whichState?: string): string[]
  • Parameters

    • whichState: string = ...

    Returns string[]

  • list_named_transitions(): Map<string, number>
  • list_states_having_action(whichState: string): string[]
  • Parameters

    • whichState: string

    Returns string[]

  • list_transitions(whichState?: string): JssmTransitionList
  • Parameters

    • whichState: string = ...

    Returns JssmTransitionList

  • lookup_transition_for(from: string, to: string): JssmTransition<mDT>
  • Parameters

    • from: string
    • to: string

    Returns JssmTransition<mDT>

  • machine_author(): string[]
  • machine_comment(): string
  • machine_contributor(): string[]
  • machine_definition(): string
  • machine_language(): string
  • machine_license(): string
  • machine_name(): string
  • machine_state(): JssmMachineInternalState<mDT>
  • Returns JssmMachineInternalState<mDT>

  • machine_version(): string
  • probabilistic_histo_walk(n: number): Map<string, number>
  • Parameters

    • n: number

    Returns Map<string, number>

  • probabilistic_transition(): boolean
  • probabilistic_walk(n: number): string[]
  • probable_action_exits(whichState?: string): any[]
  • Parameters

    • whichState: string = ...

    Returns any[]

  • probable_exits_for(whichState: string): JssmTransition<mDT>[]
  • Parameters

    • whichState: string

    Returns JssmTransition<mDT>[]

  • raw_state_declarations(): Object[]
  • set_hook(HookDesc: HookDescription): void
  • Parameters

    • HookDesc: HookDescription

    Returns void

  • sm(template_strings: TemplateStringsArray, ...remainder: any[]): Machine<mDT>
  • Parameters

    • template_strings: TemplateStringsArray
    • Rest ...remainder: any[]

    Returns Machine<mDT>

  • state(): string
  • state_declaration(which: string): JssmStateDeclaration
  • Parameters

    • which: string

    Returns JssmStateDeclaration

  • state_declarations(): Map<string, JssmStateDeclaration>
  • Returns Map<string, JssmStateDeclaration>

  • state_for(whichState: string): JssmGenericState
  • Parameters

    • whichState: string

    Returns JssmGenericState

  • state_is_complete(whichState: string): boolean
  • Parameters

    • whichState: string

    Returns boolean

  • state_is_final(whichState: string): boolean
  • Parameters

    • whichState: string

    Returns boolean

  • state_is_terminal(whichState: string): boolean
  • Parameters

    • whichState: string

    Returns boolean

  • states(): string[]
  • theme(): FslTheme
  • transition(newState: string, newData?: mDT): boolean
  • Parameters

    • newState: string
    • Optional newData: mDT

    Returns boolean

  • transition_impl(newStateOrAction: string, newData: mDT, wasForced: boolean, wasAction: boolean): boolean
  • Parameters

    • newStateOrAction: string
    • newData: mDT
    • wasForced: boolean
    • wasAction: boolean

    Returns boolean

  • valid_action(action: string, _newData?: mDT): boolean
  • Parameters

    • action: string
    • Optional _newData: mDT

    Returns boolean

  • valid_force_transition(newState: string, _newData?: mDT): boolean
  • Parameters

    • newState: string
    • Optional _newData: mDT

    Returns boolean

  • valid_transition(newState: string, _newData?: mDT): boolean
  • Parameters

    • newState: string
    • Optional _newData: mDT

    Returns boolean

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/docs/index.html b/docs/docs/index.html new file mode 100644 index 00000000..2a62e8b7 --- /dev/null +++ b/docs/docs/index.html @@ -0,0 +1,855 @@ +jssm
Options
All
  • Public
  • Public/Protected
  • All
Menu

jssm

+ +

jssm

+
+

Easy. Small. Fast. TS, es6, es5. Node, Browser. 100% coverage. Property +tests. Fuzz tests. Language tests for a dozen languages and emoji. Easy to +share online. Easy to embed.

+

Readable, useful state machines as one-liner strings.

+

Meet your new state machine library.

+ + +

TRY THE LIVE EDITOR

+ +

Discord community - Documentation - Issue tracker - CI build history

+

Discord community

+



+

Wouldn't it be nice if your TypeScript and Javascript state machines were simple and readable one-liners?

+
import { sm } from 'jssm';

const TrafficLight = sm`Red -> Green -> Yellow -> Red;`; +
+
+ +

Wouldn't it be great if they were easy to work with?

+
const log = s => console.log(s);

log( TrafficLight.state() ); // 'Red'

Machine.transition('Green'); // true
log( TrafficLight.state() ); // 'Green' +
+
+ +

What if the notation supported action names easily?

+
const TLWA = sm`Red 'next' -> Green 'next' -> Yellow 'next' -> Red;`;  // TLWA = Traffic Light With Actions

log( TLWA.state() ); // 'Red'

TLWA.action('next'); // true
log( TLWA.state() ); // 'Green'

TLWA.action('next'); // true
log( TLWA.state() ); // 'Yellow'

TLWA.action('next'); // true
log( TLWA.state() ); // 'Red' +
+
+ +

What if integration with the outside was straightforward?

+
const MTL = sm`Red 'next' -> Green 'next' -> Yellow 'next' -> Red;`  // MTL = More Traffic Lights
.hook('Red', 'Green', () => log('GO GO GO') ) // node will jump the gun when you hit return, though
.hook_entry('Red', () => log('STOP') ); // so put it on one line in node

log( MTL.state() ); // 'Red'

TLWA.action('next'); // true, console logs 'GO GO GO'
log( TLWA.state() ); // 'Green'

TLWA.action('next'); // true
log( TLWA.state() ); // 'Yellow'

TLWA.action('next'); // true, console logs 'STOP'
log( TLWA.state() ); // 'Red' +
+
+ +

What if the machine followed JS standards, and distinguished refusals as false from mistakes as thrown?

+
const ATL = sm`Red -> Green -> Yellow -> Red;`;  // ATL = Another Traffic Light

log( ATL.state() ); // 'Red' - uses 1st state unless told otherwise
ATL.transition('Yellow'); // false (Yellow isn't allowed from Red)
ATL.transition('Blue'); // throws (Blue isn't a state at all) +
+
+ +

What if there were easy convenience notations for lists, and for designating main-path => vs available path -> vs +only-when-forced ~> ?

+
const TrafficLightWithOff = sm`
Red => Green => Yellow => Red;
[Red Yellow Green] ~> Off -> Red;
`; +
+
+ +

What if that were easy to render visually?

+
const TrafficLightWithOff = sm`
Red => Green => Yellow => Red;
[Red Yellow Green] ~> Off -> Red;
`; +
+
+ + + +
+ +

What if that were easy to render visually, with styling, in PNG, JPEG, or SVG?

+
const TrafficLightWithOff = sm`
Red => Green => Yellow => Red;
[Red Yellow Green] ~> Off -> Red;

flow: left;

state Red : { background-color: pink; corners: rounded; };
state Yellow : { background-color: lightyellow; corners: rounded; };
state Green : { background-color: lightgreen; corners: rounded; };

state Off : {
background-color : steelblue;
text-color : white;
shape : octagon;
linestyle : dashed;
};
`; +
+
+ + + +
+ +

What if the machine was lighting fast, able to do tens of millions of transitions per second?

+ + +
+ +
    +
  • What if the machine and language had extensive 100% test coverage +with thousands of cases?
  • +
  • What if the machine gave extensive Typescript introspection support?
  • +
  • What if the machine had been around and active since May 2017?
  • +
  • What if the machine was MIT licensed, end to end?
  • +
+

But, above all else:

+

What if it was easy?

+



+ + +

Introducing JSSM

+
+

Meet JSSM: the Javascript State Machine.

+

State machines can make your code cleaner, safer, and more trustworthy.

+

And, with the right language, state machines can be easy and fun.

+

TRY THE LIVE EDITOR

+
+ + + +

What is JSSM?

+
+

JSSM is a Javascript state machine implementing Finite State Language, with a terse DSL and a simple API. +100% test coverage; typed with Flowtype. MIT licensed.

+

The NPM package includes pure es6, a cjs es5 bundle, and .d.ts typings. The repository includes the original typescript, the bundle, the es6, documentation, tests, tutorials, and so on.

+

Try it live!

+

Visualize with jssm-viz, or at the command line with jssm-viz-cli.

+

Language test cases for Belorussian, English, German, Hebrew, Italian, Russian, Spanish, Ukrainian, and Emoji. Please help to make sure that your language is well handled!

+
+ +

Actions Status

+

GitHub forks +GitHub watchers +GitHub stars +GitHub followers

+

License +Open issues +Closed issues +Travis status +Coveralls status

+

NPM version +CDNjs version +NPM downloads

+ + +
+ + + +



+ + +

TL;DR

+
+

Specify finite state machines with a brief syntax. Run them; they're fast. Make mistakes; they're strict. Derive +charts. Save and load states, and histories. Make machine factories to churn out dozens or thousands of instances. +Impress friends and loved ones. Cure corns and callouses.

+
Red 'Proceed' -> Green 'Proceed' -> Yellow 'Proceed' -> Red;
+
+

This will produce the following FSM (graphed with jssm-viz):

+

+

You'll build an executable state machine.

+

+



+ + +

Why

+
+

As usual, a valid question.

+
+ + + +

Why state machines

+
+

State machines are a method of making your software better able to prevent illegal states. Similar to type systems, SQL +constraints, and linters, state machines are a way to teach the software to catch mistakes in ways you define, to help +lead to better software.

+

The major mechanism of a state machine is to define states, the transitions between them, and sometimes associated +data and other niceties. The minor mechanism of state machines is to attach actions to the transitions, such that +the state machine can partially run itself.

+

+

So, to look at the same traffic light as above, you'll notice some things.

+
    +
  1. A sufficiently smart implementation will know that it's okay for Green to switch to Yellow, but not to Red
  2. +
  3. A sufficiently smart implementation knows there's no such thing as Blue
  4. +
  5. A sufficiently smart implementation knows that when in Green, to be told to Proceed means to go to Yellow, but +when in Yellow, it means to go to Red instead
  6. +
+

Along with other common sense things, a good state machine implementation can help eliminate large classes of error in +software. State machines are often applied when the stakes on having things correct are high.

+
+ + + +

Why this implementation

+
+

Brevity.

+

High quality testing. JSSM has 100% coverage, and has partial stochastic test coverage.

+

Feature parity, especially around the DSL and data control.

+

Data integrity. JSSM allows a much stricter form of state machine than is common, with a relatively low performance +and storage overhead. It also offers an extremely terse domain specific language (though it does not require said DSL) +to produce state machines in otherwise comparatively tiny and easily read code.

+



+ + +

Quick Start

+
+
+

A state machine in JSSM is defined in one of two ways: through the DSL, or through a datastructure.

+
+

So yeah, let's start by getting some terminology out of the way, and then we can go right back to that impenetrable +sentence, so that it'll make sense.

+
+ + + +

Quick Terminology

+
+

Finite state machines have been around forever, are used by everyone, and are hugely important. As a result, the +terminology is a mess, is in conflict, and is very poorly chosen, in accordince with everything-is-horrible law.

+

This section describes the terminology as used by this library. The author has done his best to choose a terminology +that matches common use and will be familiar to most. Conflicts are explained in the following section, to keep this +simple.

+

For this quick overview, we'll define six basic concepts:

+
    +
  1. Finite state machines
  2. +
  3. Machines
  4. +
  5. States
  6. +
  7. Current state
  8. +
  9. Transitions
  10. +
  11. Actions
  12. +
+

There's other stuff, of course, but these five are enough to wrap your head around finite state machines.

+
+ + + +

Basic concepts

+
+

This is a trivial traffic light FSM, with three states, three transitions, and one action:

+
Red 'Proceed' -> Green 'Proceed' -> Yellow 'Proceed' -> Red;
+
+

+

Let's review its pieces.

+
    +
  • finite state machines

    +
      +
    • A finite state machine (or FSM) is a collection of states, and rules about how you can transition between +the states.
    • +
    • We tend to refer to a design for a machine as "an FSM."
    • +
    • In this example, the traffic light's structure is "a traffic light FSM."
    • +
    +
  • +
  • states

    +
      +
    • FSMs always have at least one state, and nearly always many states
    • +
    • In this example,
        +
      • the states are Red, Yellow, and Green
      • +
      • Something made from this FSM will only ever be one of those colors - not, say, Blue
      • +
      +
    • +
    +
  • +
  • machines

    +
      +
    • Single instances of an FSM are referred to as a machine
    • +
    • We might have a thousand instances of the traffic light designed above
    • +
    • We would say "My intersection has four machines of the standard three color light FSM."
    • +
    +
  • +
  • current state

    +
      +
    • A machine has a current state, though an FSM does not
        +
      • "This specific traffic light is currently Red"
      • +
      +
    • +
    • Traffic lights in general do not have a current color, only specific lights
    • +
    • FSMs do not have a current state, only specific machines
    • +
    • A given machine will always have exactly one state - never multiple, never none
    • +
    +
  • +
  • transitions

    +
      +
    • FSMs nearly always have transitions
    • +
    • Transitions govern whether a state may be reached from another state
        +
      • This restriction is much of the value of FSMs
      • +
      +
    • +
    • In this example,
        +
      • the transitions are
          +
        • GreenYellow
        • +
        • YellowRed
        • +
        • RedGreen
        • +
        +
      • +
      • a machine whose current state is Green may switch to Yellow, because there is an appropriate transition
      • +
      • a machine whose current state is Green may not switch to Red, or to Green anew, because there is no +such transition
          +
        • A machine in Yellow which is told to transition to Green (which isn't legal) will know to refuse
        • +
        • This makes FSMs an effective tool for error prevention
        • +
        +
      • +
      +
    • +
    +
  • +
  • actions

    +
      +
    • Many FSMs have actions, which represent events from the outside world.
    • +
    • In this example, there is only one action - Proceed
        +
      • The action Proceed is available from all three colors
      • +
      +
    • +
    • At any time we may indicate to this light to go to its next color, without +taking the time to know what it is.
        +
      • This allows FSMs like the light to self-manage.
      • +
      • A machine in Yellow which is told to take the action Proceed will +know on its own to switch its current state to Red.
      • +
      • This makes FSMs an effective tool for complexity reduction
      • +
      +
    • +
    +
  • +
+

Those six ideas in hand - FSMs, states, machines, current state, transitions, and actions - and you're ready +to move forwards.

+

One other quick definition - a DSL, or domain specific language, is when someone makes a language and embeds it into +a different language, for the purpose of attacking a specific job. When React uses a precompiler to embed stuff that +looks like HTML in Javascript, that's a DSL.

+

This library implements a simple language for defining finite state machines inside of strings. For example, this +DSL defines that 'a -> b;' actually means "create two states, create a transition between them, assign the first as +the initial state", et cetera. That micro-language is the DSL that we'll be referring to a lot, coming up. This +DSL's formal name is jssm-dot, because it's a descendant-in-spirit of an older flowcharting language +DOT, from graphviz, which is also used to make the +visualizations in jssm-viz by way of viz-js.

+

Enough history lesson. On with the tooling.

+
+ + + +

And now, that Quick Start we were talking about

+
+

So let's put together a trivial four-state traffic light: the three colors, plus Off. This will give us an +opportunity to go over the basic facilities in the language.

+

At any time, you can take the code and put it into the +graph explorer for an opportunity to mess with the +code as you see fit.

+
+ + + +

0: Lights always have an off state

+
+

Our light will start in the Off state, with the ability to switch to the Red state.

+

Since that's a normal, not-notable thing, we'll just make it a regular -> legal transition.

+
Off -> Red;
+
+

We will give that transition an action, and call it TurnOn.

+
Off 'TurnOn' -> Red;
+
+

So far, our machine is simple:

+

+
+ + + +

1: Traffic lights have a three-color cycle

+
+

The main path of a traffic light is cycling from Green to Yellow, then to Red, then back again. Because +this is the main path, we'll mark these steps => main transitions.

+
Off 'TurnOn' -> Red => Green => Yellow => Red;
+
+

We will give those all the same action name, Proceed, indicating "next color" without needing to know what we're +currently on.

+
Off 'TurnOn' -> Red 'Proceed' => Green 'Proceed' => Yellow 'Proceed' => Red;
+
+

Machine's still pretty simple:

+

+
+ + + +

2: Traffic lights can be shut down

+
+

We'd also like to be able to turn this light back off. Because that's expected to be a rarity, we'll require that it +be a ~> forced transition.

+

We could write

+
Off 'TurnOn' -> Red 'Proceed' => Green 'Proceed' => Yellow 'Proceed' => Red;
Red ~> Off;
Yellow ~> Off;
Green ~> Off; +
+

But that takes a lot of space even with this short list, so, instead we'll use the array notation

+
Off 'TurnOn' -> Red 'Proceed' => Green 'Proceed' => Yellow 'Proceed' => Red;
[Red Yellow Green] ~> Off; +
+

And we'd like those all to have the action TurnOff, so

+
Off 'TurnOn' -> Red 'Proceed' => Green 'Proceed' => Yellow 'Proceed' => Red;
[Red Yellow Green] 'TurnOff' ~> Off; +
+

Machine's still not too bad:

+

+
+ + + +

Let's actually use the traffic light

+
+

That's actually the bulk of the language. There are other little add-ons here and there, but, primarily you now know +how to write a state machine.

+

Let's load it and use it! 😀

+ + +

loading into node

+
+ + +

loading into html

+
+ + +

jssm-viz

+
+ + +

redistribution on npm

+
+
+ + + +

An introduction to machine design

+
+

Let's make a state machine for ATMs. In the process, we will use a lot of core concepts of finite state machines +and of jssm-dot, this library's DSL.

+

We're going to improve on this NCSU ATM diagram that I +found:

+

+

Remember, at any time, you can take the code and put it into the +graph explorer for an opportunity to mess with the +code as you see fit.

+
+ + + +

0: Empty machine

+
+

We'll start with an empty machine.

+
EmptyWaiting 'Wait' -> EmptyWaiting;
+
+

+
+ + + +

1: Should be able to eject cards

+
+

We'll add the ability to physically eject the user's card and reset to the empty and waiting state. Right now it'll +dangle around un-used at the top, but later it'll become useful.

+

This is expressed as the path EjectCardAndReset -> EmptyWaiting;

+
EmptyWaiting 'Wait' -> EmptyWaiting;
EjectCardAndReset -> EmptyWaiting; +
+

+
+ + + +

2: Should be able to insert cards

+
+

We'll add the ability to physically insert a card, next. You know, the, uh, thing ATMs are pretty much for.

+

To get this, add the path leg EmptyWaiting 'InsertCard' -> HasCardNoAuth;

+
EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;
EjectCardAndReset -> EmptyWaiting; +
+

Notice that the new state, HasCardNoAuth, has been rendered red. This is because it is terminal - there is +no exit from this node currently. (EmptyAndWaiting did not render that way because it had a transition to itself.) +That will change as we go back to adding more nodes. terminal nodes are usually either mistakes or the last single +state of a given FSM.

+

+
+ + + +

3: Should be able to cancel and recover the card

+
+

Next, we should have a cancel, because the ATM's 7 key is broken, and we need our card back. Cancel will +exit to the main menu, and return our card credential.

+

To that end, we add the path HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;

+
EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;

HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;

EjectCardAndReset -> EmptyWaiting; +
+

+
+ + + +

4: Can give the wrong PIN

+
+

Next, let's give the ability to get the password ... wrong. 😂 Because we all know that one ATM that only has the +wrong-PIN path, so, apparently that's a product to someone.

+

When they get the PIN wrong, they're prompted to try again (or to cancel.)

+

We'll add the path HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;

+
EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;

HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;

EjectCardAndReset -> EmptyWaiting; +
+

+
+ + + +

5: Can give the correct PIN

+
+

Next, let's give the ability to get the password right.

+

We'll add two paths. The first gets the password right: HasCardNoAuth 'RightPIN' -> MainMenu;

+

The second, from our new state MainMenu, gives people the ability to leave: MainMenu 'ExitReturnCard' -> EjectCardAndReset;

+
EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;

HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
HasCardNoAuth 'RightPIN' -> MainMenu;

MainMenu 'ExitReturnCard' -> EjectCardAndReset;

EjectCardAndReset -> EmptyWaiting; +
+

+
+ + + +

6: Can check balance from main menu

+
+

Hooray, now we're getting somewhere.

+

Let's add the ability to check your balance. First pick that from the main menu, then pick which account to see the +balance of, then you're shown a screen with the information you requested; then go back to the main menu.

+

That's MainMenu 'CheckBalance' -> PickAccount -> DisplayBalance -> MainMenu;.

+
EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;

HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
HasCardNoAuth 'RightPIN' -> MainMenu;

MainMenu 'ExitReturnCard' -> EjectCardAndReset;
MainMenu 'CheckBalance' -> PickAccount -> DisplayBalance -> MainMenu;

EjectCardAndReset -> EmptyWaiting; +
+

+
+ + + +

7: Can deposit money from main menu

+
+

Let's add something difficult. Their state machine just proceeds assuming everything is okay.

+

To desposit money:

+
    +
  1. Accept physical money
  2. +
  3. If accept failed (eg door jammed,) reject physical object, go to main menu
  4. +
  5. If accept succeeded, ask human expected value
  6. +
  7. Pick an account this should go into
  8. +
  9. Contact bank. Request to credit for theoretical physical money.
  10. +
  11. Three results: yes, no, offer-after-audit.
  12. +
  13. If no, reject physical object, go to main menu.
  14. +
  15. If yes, consume physical object, tell user consumed, go to main menu
  16. +
  17. If offer-after-audit, ask human what to do
  18. +
  19. if human-yes, consume physical object, tell user consumed, go to main menu
  20. +
  21. if human-no, reject physical object, go to main menu
  22. +
+

Writing this out in code is not only generally longer than the text form, but also error prone and hard to maintain.

+

... or there's the FSM DSL, which is usually as-brief-as the text, and frequently both briefer and more explicit.

+
    +
  • Rules 1-2: MainMenu 'AcceptDeposit' -> TentativeAcceptMoney 'AcceptFail' -> RejectPhysicalMoney -> MainMenu;
  • +
  • Rules 3-6: TentativeAcceptMoney 'AcceptSucceed' -> PickDepositAccount -> RequestValue 'TellBank' -> BankResponse;
  • +
  • Rule 7: BankResponse 'BankNo' -> RejectPhysicalMoney;
  • +
  • Rule 8: BankResponse 'BankYes' -> ConsumeMoney -> NotifyConsumed -> MainMenu;
  • +
  • Rules 9-10: BankResponse 'BankAudit' -> BankAuditOffer 'HumanAcceptAudit' -> ConsumeMoney;
  • +
  • Rule 11: BankAuditOffer 'HumanRejectAudit' -> RejectPhysicalMoney;
  • +
+

Or, as a block,

+
MainMenu 'AcceptDeposit' -> TentativeAcceptMoney;

TentativeAcceptMoney 'AcceptFail' -> RejectPhysicalMoney -> MainMenu;
TentativeAcceptMoney 'AcceptSucceed' -> PickDepositAccount -> RequestValue 'TellBank' -> BankResponse;

BankResponse 'BankNo' -> RejectPhysicalMoney;
BankResponse 'BankYes' -> ConsumeMoney -> NotifyConsumed -> MainMenu;
BankResponse 'BankAudit' -> BankAuditOffer 'HumanAcceptAudit' -> ConsumeMoney;

BankAuditOffer 'HumanRejectAudit' -> RejectPhysicalMoney; +
+

Which leaves us with the total code

+
EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;

HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
HasCardNoAuth 'RightPIN' -> MainMenu;

MainMenu 'AcceptDeposit' -> TentativeAcceptMoney;
MainMenu 'ExitReturnCard' -> EjectCardAndReset;
MainMenu 'CheckBalance' -> PickCheckBalanceAccount -> DisplayBalance -> MainMenu;

TentativeAcceptMoney 'AcceptFail' -> RejectPhysicalMoney -> MainMenu;
TentativeAcceptMoney 'AcceptSucceed' -> PickDepositAccount -> RequestValue 'TellBank' -> BankResponse;

BankResponse 'BankNo' -> RejectPhysicalMoney;
BankResponse 'BankYes' -> ConsumeMoney -> NotifyConsumed -> MainMenu;
BankResponse 'BankAudit' -> BankAuditOffer 'HumanAcceptAudit' -> ConsumeMoney;

BankAuditOffer 'HumanRejectAudit' -> RejectPhysicalMoney;

EjectCardAndReset -> EmptyWaiting; +
+

+
+ + + +

8: Can withdraw money from main menu

+
+

Let's also be able to take money from the machine. After this, we'll move on, since our example is pretty squarely made +by now.

+
    +
  1. Pick a withdrawl account, or cancel to the main menu
  2. +
  3. Shown a balance, pick a withdrawl amount, or cancel to acct picker
  4. +
  5. Is the withdrawl account too high? If so go to 2
  6. +
  7. Does the machine actually have the money? If not go to 2
  8. +
  9. Otherwise confirm intent w/ human
  10. +
  11. Attempt to post the transaction.
  12. +
  13. If fail, display reason and go to 1
  14. +
  15. If succeed, dispense money and go to main menu
  16. +
+
    +
  • Rules 1-3: MainMenu -> PickWithdrawlAccount -> PickAmount -> AcctHasMoney? 'TooHighForAcct' -> PickWithdrawlAccount;
  • +
  • Rule 4: AcctHasMoney? -> MachineHasMoney? 'MachineLowOnCash' -> PickAmount;
  • +
  • Rule 5: MachineHasMoney? -> ConfirmWithdrawWithHuman 'MakeChanges' -> PickWithdrawlAmount;
  • +
  • Rule 6: ConfirmWithdrawWithHuman 'PostWithdrawl' -> BankWithdrawlResponse;
  • +
  • Rule 7: BankWithdrawlResponse 'WithdrawlFailure' -> WithdrawlFailureExplanation -> PickWithdrawlAccount;
  • +
  • Rule 8: BankWithdrawlResponse 'WithdrawlSuccess' -> DispenseMoney -> MainMenu;
  • +
+

Rule 1 canceller: PickWithdrawlAccount 'CancelWithdrawl' -> MainMenu; +Rule 2 canceller: PickWithdrawlAmount 'SwitchAccounts' -> PickWithdrawlAccount;

+

Or as a whole, we're adding

+
MainMenu -> PickWithdrawlAccount -> PickAmount -> AcctHasMoney? 'TooHighForAcct' -> PickWithdrawlAccount;
AcctHasMoney? -> MachineHasMoney? 'MachineLowOnCash' -> PickAmount;
MachineHasMoney? -> ConfirmWithdrawWithHuman 'MakeChanges' -> PickWithdrawlAmount;
ConfirmWithdrawWithHuman 'PostWithdrawl' -> BankWithdrawlResponse;
BankWithdrawlResponse 'WithdrawlFailure' -> WithdrawlFailureExplanation -> PickWithdrawlAccount;
BankWithdrawlResponse 'WithdrawlSuccess' -> DispenseMoney -> MainMenu;

PickWithdrawlAccount 'CancelWithdrawl' -> MainMenu;
PickWithdrawlAmount 'SwitchAccounts' -> PickWithdrawlAccount; +
+

Which leaves us with

+
EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;

HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
HasCardNoAuth 'RightPIN' -> MainMenu;

MainMenu 'AcceptDeposit' -> TentativeAcceptMoney;
MainMenu 'ExitReturnCard' -> EjectCardAndReset;
MainMenu 'CheckBalance' -> PickCheckBalanceAccount -> DisplayBalance -> MainMenu;

TentativeAcceptMoney 'AcceptFail' -> RejectPhysicalMoney -> MainMenu;
TentativeAcceptMoney 'AcceptSucceed' -> PickDepositAccount -> RequestValue 'TellBank' -> BankResponse;

BankResponse 'BankNo' -> RejectPhysicalMoney;
BankResponse 'BankYes' -> ConsumeMoney -> NotifyConsumed -> MainMenu;
BankResponse 'BankAudit' -> BankAuditOffer 'HumanAcceptAudit' -> ConsumeMoney;

BankAuditOffer 'HumanRejectAudit' -> RejectPhysicalMoney;

MainMenu -> PickWithdrawlAccount -> PickAmount -> AcctHasMoney? 'TooHighForAcct' -> PickWithdrawlAccount;
AcctHasMoney? -> MachineHasMoney? 'MachineLowOnCash' -> PickAmount;
MachineHasMoney? -> ConfirmWithdrawWithHuman 'MakeChanges' -> PickWithdrawlAmount;
ConfirmWithdrawWithHuman 'PostWithdrawl' -> BankWithdrawlResponse;
BankWithdrawlResponse 'WithdrawlFailure' -> WithdrawlFailureExplanation -> PickWithdrawlAccount;
BankWithdrawlResponse 'WithdrawlSuccess' -> DispenseMoney -> MainMenu;

PickWithdrawlAccount 'CancelWithdrawl' -> MainMenu;
PickWithdrawlAmount 'SwitchAccounts' -> PickWithdrawlAccount;

EjectCardAndReset -> EmptyWaiting; +
+

+

As you can see, building up even very complex state machines is actually relatively straightforward, in a short +amount of time.

+



+ + +

Features

+
+ + +

DSL

+
+ + +

States

+
+ + +

Transitions

+
+ + +

Cycles

+
+ + +

Stripes

+
+ + +

Named Ordered Lists

+
+ + +

Atoms

+
+ + +

Strings

+
+ + +

Arrow types

+
+ + +

Unicode representations

+
+ + +

Node declarations

+
+ + +

All the styling bullshit

+
+ + +

Named edges

+
+ + +

URL callouts

+
+ + +

The 9 or whatever directives

+
+ + +

How to publish a machine

+
+ + +

Legal, main, and forced

+
+ + +

Validators

+
+ + +

State history

+
+ + +

Automatic visualization

+
+



+ + +

How to think in state machines

+
+



+ + +

Example Machines

+
+ + +

Door lock

+
+ + +

Traffic lights

+
+ + +

Basic three-state

+
+ + +

RYG, Off, Flash-red, Flash-yellow

+
+ + +

RYG, Off, Flash-red, Flash-yellow, Green-left, Yellow-left

+
+ + +

Heirarchal intersection

+
+ + +

ATM

+ + + +

HTTP

+ + + +

Better HTTP

+
+ + +

TCP

+ + + +

Coin-op vending machine (data)

+
+ + +

Video games

+
+ + +

Pac-man Ghost (sensors)

+
+ + +

Weather (probabilistics)

+
+ + +

Roguelike monster (interface satisfaction)

+
+ + +

Candy crush clone game flow (practical large use)

+
+ + +

Vegas locked 21 dealer behavior

+
+ + +

React SPA website (practical large use)

+
+ + +

BGP

+ + + +

LibGCrypt FIPS mode FSM

+ +



+ + +

How to debug

+
+



+ + +

How to publish

+
+

It's really quite simple.

+
    +
  1. Make a github repository.
  2. +
  3. Put your code in a file inside, with the extension .fsl
  4. +
  5. Make sure your code contains a machine_name
  6. +
+

Once done, your work should show up here.

+



+ + +

Notation Comparison

+
+ + +

Their notations, one by one

+
+ + +

Apples to Apples - Traffic Light

+
+



+ + +

Other state machines

+
+

There are a lot of state machine impls for JS, many quite a bit more mature than this one. Here are some options:

+
    +
  1. Finity 😮
  2. +
  3. Stately.js
  4. +
  5. machina.js
  6. +
  7. Pastafarian
  8. +
  9. Henderson
  10. +
  11. fsm-as-promised
  12. +
  13. state-machine
  14. +
  15. mood
  16. +
  17. FSM Workbench
  18. +
  19. SimpleStateMachine
  20. +
  21. shime/micro-machine
      +
    1. soveran/micromachine (ruby)
    2. +
    +
  22. +
  23. fabiospampinato/FSM
  24. +
  25. HQarroum/FSM
  26. +
  27. Finite-State-Automata
  28. +
  29. finite-state-machine
  30. +
  31. nfm
  32. +
+

And some similar stuff:

+
    +
  1. redux-machine
  2. +
  3. ember-fsm
  4. +
  5. State machine cat
  6. +
  7. Workty 😮
  8. +
  9. sam-simpler
  10. +
  11. event_chain
  12. +
  13. DRAKON
  14. +
  15. Yakindu Statechart Tools
  16. +
  17. GraphViz
      +
    1. Viz.js, which we use
    2. +
    +
  18. +
+




+ + +

Thanks

+
+

JSSM and FSL have had a lot of help.

+



+ + +

Internationalization

+
+ +

If I've overlooked you, please let me know.

+

If you'd like to help, it's straightforward.

+
    +
  1. Easy mode: open a PR with this file translated into your language
  2. +
  3. Extra mile: create a new repo containing this file translated
  4. +
+



+ + +

Code and Language

+
+

Forest Belton has provided guidance, bugfixes, parser and language commentary.

+

Jordan Harbrand suggested two interesting features and provided strong feedback on the initial tutorial draft.

+

The biggest thanks must go to Michael Morgan, who has debated significant sections of +the notation, invented several concepts and operators, helped with the parser, with system nomenclature, for having published +the first not-by-me FSL machine, for encouragement, and generally just for having been as interested as he has been.

+

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/docs/modules.html b/docs/docs/modules.html new file mode 100644 index 00000000..9be31c6e --- /dev/null +++ b/docs/docs/modules.html @@ -0,0 +1,96 @@ +jssm
Options
All
  • Public
  • Public/Protected
  • All
Menu

jssm

Index

Variables

gviz_shapes: string[] = ...
histograph: Function = ...
+

Returns the histograph of an array as a Map. Makes no attempt to cope + with deep equality; will fail for complex contents, as such.

+
import { histograph } from './jssm';

histograph( [0, 0, 1, 1, 2, 2, 1] ); // Map() +
+
named_colors: string[] = ...
shapes: string[] = gviz_shapes
version: string = "5.65.5"
weighted_histo_key: Function = ...
weighted_rand_select: Function = ...
weighted_sample_select: Function = ...

Functions

  • arrow_direction(arrow: JssmArrow): JssmArrowDirection
  • +

    Return the direction of an arrow - right, left, or both.

    +
    import { arrow_direction } from './jssm';

    arrow_direction('->'); // 'right'
    arrow_direction('<~=>'); // 'both' +
    +

    Parameters

    • arrow: JssmArrow

    Returns JssmArrowDirection

  • arrow_left_kind(arrow: JssmArrow): JssmArrowKind
  • +

    Return the direction of an arrow - right, left, or both.

    +
    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' +
    +

    Parameters

    • arrow: JssmArrow

    Returns JssmArrowKind

  • arrow_right_kind(arrow: JssmArrow): JssmArrowKind
  • +

    Return the direction of an arrow - right, left, or both.

    +
    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' +
    +

    Parameters

    • arrow: JssmArrow

    Returns JssmArrowKind

  • compile<mDT>(tree: JssmParseTree): JssmGenericConfig<mDT>
  • +

    Compile a machine's JSON intermediate representation to a config object. If + you're using this (probably don't,) you're probably also using + parse to get the IR, and the object constructor + {@link Machine.construct} to turn the config object into a workable machine.

    +
    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 sm:

    +
    import { sm } from './jssm';

    const switch = sm`on <=> off;`; +
    +

    Method {@link from}:

    +
    import * as jssm from './jssm';

    const toggle = jssm.from('up <=> down;'); +
    +

    Type parameters

    • mDT

    Parameters

    • tree: JssmParseTree

    Returns JssmGenericConfig<mDT>

  • from<mDT>(MachineAsString: string, ExtraConstructorFields?: Partial<JssmGenericConfig<mDT>>): Machine<mDT>
  • +

    Create a state machine from an implementation string. This is one of the + two main paths for working with JSSM, alongside 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.

    +
    import * as jssm from './jssm';

    const switch = jssm.from('on <=> off;'); +
    +

    Type parameters

    • mDT

    Parameters

    • MachineAsString: string
    • Optional ExtraConstructorFields: Partial<JssmGenericConfig<mDT>>

    Returns Machine<mDT>

  • make<mDT>(plan: string): JssmGenericConfig<mDT>
  • +

    An internal convenience wrapper for parsing then compiling a machine string. + Not generally meant for external use. Please see compile or + sm.

    +

    Type parameters

    • mDT

    Parameters

    • plan: string

    Returns JssmGenericConfig<mDT>

  • parse(input: string, options?: Object): any
  • +

    This method wraps the parser call that comes from the peg grammar, + 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 compile and Machine.constructor.

    +
    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 sm:

    +
    import { sm } from './jssm';

    const switch = sm`on <=> off;`; +
    +

    Method {@link from}:

    +
    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.

    +

    Parameters

    • input: string
    • Optional options: Object

    Returns any

  • seq(n: number): number[]
  • +

    Returns, for a non-negative integer argument n, the series [0 .. n].

    +
    import { seq } from './jssm';

    seq(5); // [0, 1, 2, 3, 4]
    seq(0); // [] +
    +

    Parameters

    • n: number

    Returns number[]

  • sm<mDT>(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.

    +
    import * as jssm from './jssm';

    const switch = jssm.from('on <=> off;'); +
    +

    Type parameters

    • mDT

    Parameters

    • template_strings: TemplateStringsArray
    • Rest ...remainder: any[]

    Returns Machine<mDT>

  • transfer_state_properties(state_decl: JssmStateDeclaration): JssmStateDeclaration
  • +

    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.

    +

    Parameters

    • state_decl: JssmStateDeclaration

    Returns JssmStateDeclaration

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..30b9fef5 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + +

JSSM repo site

+ +

There isn't actually anything here. You're probably looking for:

+ + + +

+ +

Less commonly, you might be looking for

+ + + + + + \ No newline at end of file diff --git a/docs/typedoc-addon.css b/docs/typedoc-addon.css new file mode 100644 index 00000000..5b934f71 --- /dev/null +++ b/docs/typedoc-addon.css @@ -0,0 +1,5 @@ + +:root { --color-background: #def; } + +body .tsd-page-title { background-color: #bdf; } +body .tsd-page-toolbar { background-color: #acf; border-bottom-color: #9be; } diff --git a/package.json b/package.json index 4689679b..9fd4d170 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "min_iife": "mv dist/jssm.es5.iife.js dist/jssm.es5.iife.nonmin.js && terser dist/jssm.es5.iife.nonmin.js > dist/jssm.es5.iife.js", "min_cjs": "mv dist/jssm.es5.cjs.js dist/jssm.es5.cjs.nonmin.js && terser dist/jssm.es5.cjs.nonmin.js > dist/jssm.es5.cjs.js", "site": "cp src/site/* docs/", - "docs": "typedoc src/ts/jssm.ts --out docs/docs", + "docs": "typedoc src/ts/jssm.ts --out docs/docs --customCss ./src/site/typedoc-addon.css", "changelog": "rm -f CHANGELOG.md && changelog-maker -a > CHANGELOG.md" }, "repository": { diff --git a/src/buildjs/inject_css_tag_into_docs.js b/src/buildjs/inject_css_tag_into_docs.js new file mode 100644 index 00000000..8c401a44 --- /dev/null +++ b/src/buildjs/inject_css_tag_into_docs.js @@ -0,0 +1 @@ +inject_css_tag_into_docs.js \ No newline at end of file diff --git a/src/site/typedoc-addon.css b/src/site/typedoc-addon.css new file mode 100644 index 00000000..5b934f71 --- /dev/null +++ b/src/site/typedoc-addon.css @@ -0,0 +1,5 @@ + +:root { --color-background: #def; } + +body .tsd-page-title { background-color: #bdf; } +body .tsd-page-toolbar { background-color: #acf; border-bottom-color: #9be; }