FSL for Elixir

The Elixir implementation of the Finite State Language: describe a process as a finite state machine — states, transitions, on_events and its selective receive, stay, goto back, sub-FSMs and cooperative shutdown, service building blocks, a live registry, a sequence journal and its PlantUML and Mermaid renderers — and plug a protocol into it.

defmodule Turnstile do
use FSL.Machine
config label: "gate 3"
state initial_state do
goto locked
end
state locked do
on_events do
{:coin, _} -> goto unlocked, "paid"
{:push, _} -> stay("still locked")
after
60_000 -> scenario_success("nobody came")
end
end
state unlocked do
on_events do
{:push, _} -> goto locked, "went through"
after
10_000 -> goto locked, "timed out"
end
end
end
FSL.Runner.run_instance(Turnstile)

The core depends on nothing but Logger and OTP.FSL.HTTP is the one module that needs a client, and Req is declared optional: true for it.

Install

def deps do
[{:fsl, "~> 0.2", hex: :finite_state_language}]
end

Three names for one thing: finite_state_language is the hex package, :fsl is the OTP application, FSL.* is what the code writes. The dep line has to carry the first two precisely because they differ — the atom names the application mix builds and resolves, hex: names the package it is fetched from. Naming only the package would send mix looking for a finite_state_language.app that no build produces, and mix deps.get would fail on the first try. Documentation is on hexdocs.

Run something

mix run samples/fishing.exs

Bob goes fishing. Ducks paddle past, the line snags, two fish bite, and he has 400 ms to strike each time — his hands, which are a sub-FSM with a reaction time of their own, take 250 ms. The trip prints itself as a Mermaid diagram you can paste into a GitHub comment. samples/ is the file to read first, and it is commented for that.

The governing idea

The context belongs to FSL, and a protocol binding extends it. The six fields a machine keeps about itself — lasterr, errorreason, currentstate, laststate, parent_pid, appdata — are FSL.Context; a binding splices them into its own struct and adds what a session of its protocol holds.

Everything the language must not know is a callback on FSL.Host: what to bootstrap, how to read a config block, what to do with each received event, how to categorize one, which clause every wait must carry, what to release at the end. A machine that names no host gets FSL.Host.Default and runs with no protocol at all. FSL.Test.Host (test/support/) is the worked example, and what this package's own suite runs against.

The test of every seam is whether a second binding — XMPP, Matrix, a chatbot framework — could be written without touching FSL. SIP is the first, in Elixip: SIP.FSL.Host is ~380 lines and SIP.Scenario is the 100-line facade a SIP scenario writes.

Documentation

Licence

Apache-2.0. The code was extracted from Elixip, which stays BUSL-1.1, and relicensed so that both implementations of the language ship under one licence — see NOTICE.