Packages
finitomata
0.24.3
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/hook.ex
defmodule Finitomata.Hook do
@moduledoc false
alias Finitomata.Mix.Events
@type ast_meta :: keyword()
@type ast_tuple :: {atom(), ast_meta(), any()}
@type t :: %{
__struct__: __MODULE__,
env: Macro.Env.t(),
module: module(),
kind: :def | :defp,
fun: atom(),
arity: arity(),
args: [ast_tuple()],
guards: [ast_tuple()],
body: [{:do, ast_tuple()}]
}
defstruct ~w|env module kind fun arity args guards body|a
defimpl Inspect do
@moduledoc false
import Inspect.Algebra
@spec inspect(Finitomata.Hook.t(), Inspect.Opts.t()) ::
:doc_line
| :doc_nil
| binary
| {:doc_collapse, pos_integer}
| {:doc_force, any}
| {:doc_break | :doc_color | :doc_cons | :doc_fits | :doc_group | :doc_string, any,
any}
| {:doc_nest, any, :cursor | :reset | non_neg_integer, :always | :break}
def inspect(%Finitomata.Hook{module: module, fun: fun, args: args, guards: guards}, opts) do
case Keyword.get(opts.custom_options, :fancy, true) do
false ->
inner = [module: module, fun: fun, args: args, guards: guards]
concat(["#Finitomata.Hook<", to_doc(inner, opts), ">"])
_ ->
args = args |> Macro.to_string() |> String.slice(1..-2//1)
guards =
case guards do
[] -> ""
guards -> " when " <> (guards |> Macro.to_string() |> String.slice(1..-2//1))
end
concat([" ↹‹#{inspect(module)}.#{fun}(", args, ")", guards, "›"])
end
end
end
def __on_definition__(env, :def, :on_transition, [_, _, _, _] = args, guards, body) do
if compiler?() do
Events.put(
:hooks,
struct(__MODULE__,
env: env,
module: env.module,
kind: :def,
fun: :on_transition,
arity: 4,
args: args,
guards: guards,
body: body
)
)
end
end
def __on_definition__(_env, _kind, _fun, _args, _guards, _body), do: :ok
defmacro __before_compile__(env) do
if Code.ensure_loaded?(Mix) do
deps? =
[depth: 1]
|> Mix.Project.deps_scms()
|> Map.take([:finitomata, :siblings])
|> map_size()
|> Kernel.>(0)
if not compiler?() and deps? do
Mix.shell().info([
[:bright, :yellow, "warning: ", :reset],
"unhandled finitomata declaration found in ",
[:bright, :blue, inspect(env.module), :reset],
",\n but we were unable to analyse the correctness of the FSM.\n Add ",
[:bright, :cyan, ":finitomata", :reset],
" compiler to ",
[:bright, :cyan, "compilers:", :reset],
" in your ",
[:bright, :cyan, "mix.exs", :reset],
"!\n #{Path.relative_to_cwd(env.file)}:#{env.line}"
])
end
end
quote generated: true,
location: :keep,
bind_quoted: [module: env.module, file: env.file, line: env.line] do
states = @__config__[:states]
@typedoc """
Kind of event which might be send to initiate the transition.
## FSM representation
```#{@__config__[:syntax] |> Module.split() |> List.last() |> Macro.underscore()}
#{@__config__[:syntax].lint(@__config__[:dsl])}
```
"""
@type state :: unquote(Enum.reduce(states, &{:|, [], [&1, &2]}))
require Logger
if :on_transition in @__config__[:impl_for] do
@impl Finitomata
def on_transition(current, event, event_payload, state_payload) do
Logger.debug(
"[✓ ⇄] with: " <>
inspect(
current: current,
event: event,
event_payload: event_payload,
state: state_payload
)
)
case Finitomata.Transition.allowed(@__config__[:fsm], current, event) do
[new_current] -> {:ok, new_current, state_payload}
[] -> {:error, {:undefined_transition, {current, event}}}
other -> {:error, {:ambiguous_transition, {current, event}, other}}
end
end
end
if :on_failure in @__config__[:impl_for] do
@impl Finitomata
def on_failure(event, payload, state) do
Logger.warning("[✗ ↹] " <> inspect(state: state, event: event, event_payload: payload))
end
end
if :on_enter in @__config__[:impl_for] do
@impl Finitomata
def on_enter(entering, state) do
Logger.debug("[← ↹] " <> inspect(state: state, entering: entering))
end
end
if :on_exit in @__config__[:impl_for] do
@impl Finitomata
def on_exit(exiting, state) do
Logger.debug("[→ ↹] " <> inspect(state: state, exiting: exiting))
end
end
if :on_terminate in @__config__[:impl_for] do
@impl Finitomata
def on_terminate(state) do
Logger.info("[◉ ↹] " <> inspect(state: state))
end
end
if :on_timer in @__config__[:impl_for] do
@impl Finitomata
def on_timer(current_state, state) do
Logger.debug("[✓ ↹] " <> inspect(current_state: current_state, state: state))
end
end
if is_integer(@__config__[:timer]) and not Module.defines?(module, {:on_timer, 2}) do
raise CompileError,
file: Path.relative_to_cwd(file),
line: line,
description:
"when `timer: non_neg_integer()` is given to `use Finitomata` " <>
"there must be `on_timer/2` callback defined"
end
end
end
@spec details(t()) :: [
{:args, [ast_tuple()]} | {:body, [{:do, ast_tuple()}]} | {:guards, [ast_tuple()]}
]
@doc false
def details(%__MODULE__{args: args, guards: guards, body: body}),
do: [args: args, guards: guards, body: body]
@spec compiler? :: boolean()
defp compiler? do
Events
|> Process.whereis()
|> then(fn
pid when is_pid(pid) -> Process.alive?(pid)
_ -> false
end)
end
end