README.md

# StateChart

<!--- StateChart moduledoc start -->

A pure-Elixir implementation of statecharts inspired by

- David Harel's [Statecharts: a visual formalism for complex systems](https://www.sciencedirect.com/science/article/pii/0167642387900359) paper
- David Khourshid's JavaScript [XState](https://xstate.js.org/docs/) library

## Installation

This package can be installed by adding `statechart` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:statechart, "~> 0.1.0"}
  ]
end
```

## Concepts

We'll model a simple traffic light to illustrate some statechart concepts.

![traffic light diagram](assets/traffic_light.jpg)

- This "machine" defaults to the `off` state (that's what the dot-arrow signifies).
- If we then send the machine a `TOGGLE` event, it transitions to the `on` state.
  From there, it automatically drops into the `red` state (again, because of the dot-arrow).
  At this point, the machine is in both the `on` and `red` states.
- If we send it a `NEXT` event, we transition to the `green` state (which you can also think of as the `on/green` state).
  Another `NEXT` event, and we transition to the `yellow` state.
  In this way, the light will just keep cycling through the colors.
- If we send it a `TOGGLE` at this point, it will transition back to `off`.
- If we now send the machine a `NEXT` event (while it's in the `off` state), nothing happens.

## Usage

There are three steps to modeling via the `Statechart` library:
- **DEFINE**
  - `statechart/2`
  - `state/3`
  - `>>>/2`
- **INSTANTIATE**
  - `MyStatechart.new/0`
- **MANIPULATE**
  - `trigger/2`
  - `states/1`
  - `in_state?/2`
  - `last_event_status/1`

We'll model the above traffic light using these three steps.

### DEFINE

```elixir
defmodule TrafficLight do
  use Statechart

  statechart default: :off do
    state :off do
      :TOGGLE >>> :on
    end

    state :on, default: :red do
      :TOGGLE >>> :off
      state :red,    do: :NEXT >>> :green
      state :yellow, do: :NEXT >>> :red
      state :green,  do: :NEXT >>> :yellow
    end
  end
end
```

### INSTANTIATE

The module containing your statechart definition automatically has a `new/0` function injected into it.

```elixir
traffic_light = TrafficLight.new()
```

It returns you a `t:statechart/0` struct that you then pass to all the 'MANIPULATE' functions.

### MANIPULATE

The machine starts in the `off` state:
```elixir
[:off] = Statechart.states(traffic_light)
true   = Statechart.in_state?(traffic_light, :off)
false  = Statechart.in_state?(traffic_light, :on)
```

Send it a `NEXT` event without it being on yet:
```elixir
traffic_light = Statechart.trigger(traffic_light, :NEXT)
# Still off...
true = Statechart.in_state?(traffic_light, :off)
# ...but we can see that the last event wasn't valid:
:error = Statechart.last_event_status(traffic_light)
```

Let's turn it on:
```elixir
traffic_light = Statechart.trigger(traffic_light, :TOGGLE)
[:on, :red]   = Statechart.states(traffic_light)
true  = Statechart.in_state?(traffic_light, :on)
true  = Statechart.in_state?(traffic_light, :red)
false = Statechart.in_state?(traffic_light, :off)
false = Statechart.in_state?(traffic_light, :green)
```

**Now** the `NEXT` events will have an effect:
```elixir
traffic_light = Statechart.trigger(traffic_light, :NEXT)
[:on, :green] = Statechart.states(traffic_light)
```

## Error-checking

`Statechart` has robust compile-time checking.
For example, compiling this module will result in a `StatechartError`
at the `state :on` line.

```elixir
defmodule ToggleStatechart do
  use Statechart

  statechart default: :on do
    # Whoops! We've misspelled "off":
    state :on, do: :TOGGLE >>> :of
    state :off, do: :TOGGLE >>> :on
  end
end
```

## Other statechart / state machine libraries

With a plethora of other related libraries,
why did we need another one?
I wanted one that had very strict compile-time checks and a simple DSL.

Other libraries you might look into:
- `Machinery`
- `as_fsm`
- `gen_statem`
- https://github.com/sasa1977/fsm


<!--- StateChart moduledoc end -->

## Roadmap

- [X] compound states
- [ ] orthogonal states
- [ ] composability via subcharts
- [ ] on-enter and on-exit actions
- [ ] custom 'data' state
- [ ] final state
- [ ] state history (when re-entering a state, default to where you left off)
- [ ] transition history

## Tasks for 0.1.0 release

- add typespecs for public functions
- Make sure all Statechart functions/macros have a one-liner summary