Packages
finitomata
0.27.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/plant_uml.ex
defmodule Finitomata.PlantUML do
@moduledoc false
import NimbleParsec
alias Finitomata.Parser
@behaviour Parser
@alphanumeric [?a..?z, ?A..?Z, ?0..?9, ?_]
blankspace = ignore(ascii_string([?\s], min: 1))
transition_op = string("-->")
event_op = string(":")
event =
ascii_char([?a..?z])
|> optional(ascii_string(@alphanumeric, min: 1))
|> optional(ascii_char([??, ?!]))
|> reduce({IO, :iodata_to_binary, []})
state = choice([string("[*]"), event])
target_states = state |> concat(ignore(string(","))) |> times(min: 0) |> concat(state)
plant_line =
optional(blankspace)
|> concat(state)
|> ignore(blankspace)
|> ignore(transition_op)
|> ignore(blankspace)
|> concat(target_states)
|> ignore(blankspace)
|> ignore(event_op)
|> ignore(blankspace)
|> concat(event)
|> optional(blankspace)
|> ignore(choice([times(string("\n"), min: 1), times(string("\r\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.PlantUML.transition("state1 --> state2 : succeeded")
iex> result
[transition: ["state1", "state2", "succeeded"]]
iex> {:ok, result, _, _, _, _} = Finitomata.PlantUML.transition("state1 --> state2,state3 : succeeded")
iex> result
[transition: ["state1", "state2", "state3", "succeeded"]]
iex> {:error, message, _, _, _, _} = Finitomata.PlantUML.transition("state1 --> State2 : succeeded")
iex> String.slice(message, 0..14)
"expected string"
"""
defparsec(:transition, plant_line)
@doc ~S"""
iex> Finitomata.PlantUML.transitions("state1 --> state2,state3 : succeeded")
[transition: ["state1", "state2", "succeeded"], transition: ["state1", "state3", "succeeded"]]
"""
def transitions({:transition, [from | tos_event]}) do
[event | tos] = Enum.reverse(tos_event)
for to <- Enum.reverse(tos), do: {:transition, [from, to, event]}
end
def transitions(plantuml_line) do
with {:ok, [{:transition, _ts} = transitions], _, _, _, _} <- transition(plantuml_line) do
transitions(transitions)
end
end
@doc ~S"""
iex> {:ok, result, _, _, _, _} = Finitomata.PlantUML.do_fsm("s1 --> s2 : ok\ns2 --> [*] : ko")
iex> result
[transition: ["s1", "s2", "ok"], transition: ["s2", "[*]", "ko"]]
"""
defparsec(:do_fsm, times(choice([plant_line, malformed]), min: 1))
@doc ~S"""
iex> {:ok, result, _, _, _, _} = Finitomata.PlantUML.fsm("s1 --> s2 : ok\ns2 --> s3,[*] : ko ")
iex> result
[transition: ["s1", "s2", "ok"], transition: ["s2", "s3", "ko"], transition: ["s2", "[*]", "ko"]]
"""
def fsm(input) when is_binary(input) do
with {:ok, result, rest, opts, pos, count} <- do_fsm(input),
do: {:ok, Enum.flat_map(result, &transitions/1), rest, opts, pos, count}
end
@doc ~S"""
iex> {:ok, result, _, _, _, _} = Finitomata.PlantUML.fsm("s1 --> s2 : ok\ns2 --> [*] : ko")
...> Finitomata.PlantUML.validate(result)
{:error, :initial_state}
iex> {:ok, result, _, _, _, _} = Finitomata.PlantUML.fsm("[*] --> s1 : foo\ns1 --> s2 : ok\ns2 --> [*] : ko")
...> Finitomata.PlantUML.validate(result)
{:ok,
[
%Finitomata.Transition{event: :foo, from: :*, to: :s1},
%Finitomata.Transition{event: :ok, from: :s1, to: :s2},
%Finitomata.Transition{event: :ko, from: :s2, to: :*}
]}
"""
@impl Parser
def validate(parsed, env \\ __ENV__), do: Finitomata.validate(parsed, env)
@doc ~S"""
iex> Finitomata.PlantUML.parse("[*] --> s1 : ok\ns2 --> [*] : ko")
{:error, :orphan_from_state}
iex> Finitomata.PlantUML.parse("[*] --> s1 : foo\ns1 --> s2 : ok\ns2 --> [*] : ko")
{:ok,
[
%Finitomata.Transition{event: :foo, from: :*, to: :s1},
%Finitomata.Transition{event: :ok, from: :s1, to: :s2},
%Finitomata.Transition{event: :ko, from: :s2, 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: "@startuml\n\n" <> input <> "\n@enduml"
@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 --> to : event`"}
end
end