Packages
finitomata
0.17.0
0.41.0
0.40.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.1
0.30.3
0.30.2
0.30.1
0.30.0
0.29.10
0.29.9
0.29.8
0.29.7
0.29.6
0.29.5
0.29.4
0.29.3
0.29.2
0.29.1
0.29.0
0.28.1
0.28.0
0.27.1
0.27.0
0.26.4
0.26.3
0.26.2
0.26.1
0.26.0
0.25.0
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.7
0.23.6
0.23.5
0.23.4
0.23.3
0.23.2
0.23.1
0.23.0
0.22.1
0.22.0
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.1
0.17.0
0.16.0
0.15.1
0.15.0
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.1
0.12.0
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.1
0.1.0
The FSM implementation generated from PlantUML textual representation.
Current section
Files
Jump to
Current section
Files
lib/finitomata/parser/mermaid.ex
defmodule Finitomata.Mermaid do
@moduledoc false
import NimbleParsec
alias Finitomata.Parser
@behaviour Parser
@alphanumeric [?a..?z, ?A..?Z, ?0..?9, ?_]
blankspace = ignore(ascii_string([?\s], min: 1))
semicolon = ignore(string(";"))
transition_op = string("-->")
identifier =
ascii_char([?a..?z])
|> optional(ascii_string(@alphanumeric, min: 1))
|> optional(ascii_char([??, ?!]))
|> reduce({IO, :iodata_to_binary, []})
state = identifier
event = ignore(string("|")) |> concat(identifier) |> ignore(string("|"))
mermaid_line =
optional(blankspace)
|> concat(state)
|> ignore(blankspace)
|> ignore(transition_op)
|> ignore(blankspace)
|> concat(event)
|> ignore(blankspace)
|> concat(state)
|> optional(blankspace)
|> optional(semicolon)
|> ignore(choice([times(string("\n"), min: 1), eos()]))
|> tag(:transition)
malformed =
optional(utf8_string([not: ?\n], min: 1))
|> string("\n")
|> pre_traverse(:abort)
@doc ~S"""
iex> {:ok, result, _, _, _, _} = Finitomata.Mermaid.transition("state1 --> |succeeded| state2")
iex> result
[transition: ["state1", "succeeded", "state2"]]
iex> {:error, message, _, _, _, _} = Finitomata.Mermaid.transition("state1 --> |succeeded| State2")
iex> String.slice(message, 0..13)
"expected ASCII"
"""
defparsec(:transition, mermaid_line)
@doc ~S"""
iex> {:ok, result, _, _, _, _} = Finitomata.Mermaid.fsm("s1 --> |ok| s2;\ns2 --> |ko| s3")
iex> result
[transition: ["s1", "ok", "s2"], transition: ["s2", "ko", "s3"]]
"""
defparsec(:fsm, times(choice([mermaid_line, malformed]), min: 1))
@doc ~S"""
iex> {:ok, result, _, _, _, _} = Finitomata.Mermaid.fsm("s1 --> |ok| s2\ns2 --> |ko| s3")
...> Finitomata.Mermaid.validate(result)
{:ok,
[
%Finitomata.Transition{event: :__start__, from: :*, to: :s1},
%Finitomata.Transition{event: :ok, from: :s1, to: :s2},
%Finitomata.Transition{event: :ko, from: :s2, to: :s3},
%Finitomata.Transition{event: :__end__, from: :s3, to: :*}
]}
"""
@impl Parser
def validate(parsed, env \\ __ENV__) do
parsed =
Enum.map(parsed, fn {:transition, [from, event, to]} -> {:transition, [from, to, event]} end)
from_states = parsed |> Enum.map(fn {:transition, [from, _, _]} -> from end) |> Enum.uniq()
to_states = parsed |> Enum.map(fn {:transition, [_, to, _]} -> to end) |> Enum.uniq()
start_states =
Enum.map(from_states -- to_states, fn from -> {:transition, ["[*]", from, "__start__"]} end)
final_states =
Enum.map(to_states -- from_states, fn to -> {:transition, [to, "[*]", "__end__"]} end)
amended = start_states ++ parsed ++ final_states
Finitomata.validate(amended, env)
end
@doc ~S"""
iex> Finitomata.Mermaid.parse("s1 --> |ok| s2\ns2 --> |ko| s3")
{:ok,
[
%Finitomata.Transition{event: :__start__, from: :*, to: :s1},
%Finitomata.Transition{event: :ok, from: :s1, to: :s2},
%Finitomata.Transition{event: :ko, from: :s2, to: :s3},
%Finitomata.Transition{event: :__end__, from: :s3, to: :*}
]}
"""
@impl Parser
def parse(input, env \\ __ENV__) do
case fsm(input) do
{:ok, result, _, _, _, _} ->
validate(result, env)
{:error, "[line: " <> _ = msg, _rest, context, _, _} ->
[numbers, msg] = String.split(msg, "|||")
{numbers, []} = Code.eval_string(numbers)
{:error, msg, numbers[:content], context, {env.file, env.line, 0}, numbers[:offset]}
error ->
error
end
end
@impl Parser
def lint(input) when is_binary(input) do
input = input |> String.split("\n", trim: true) |> Enum.map_join("\n", &(" " <> &1))
"graph TD\n" <> input
end
@spec abort(
String.t(),
[String.t()],
map(),
{non_neg_integer, non_neg_integer},
non_neg_integer
) :: {:error, binary()}
defp abort(rest, content, _context, {line, column}, offset) do
content = content |> Enum.reverse() |> Enum.join() |> String.trim()
meta = inspect(line: line, column: column, offset: offset, rest: rest, content: content)
{:error,
meta <>
"|||malformed FSM transition (line: #{line}, column: #{column - offset}), expected `from --> |event| to`"}
end
end