Packages

Makina is a DSL for writing PBT models for stateful systems.

Current section

Files

Jump to
makina lib makina import.ex
Raw

lib/makina/import.ex

defmodule Makina.Import do
import Makina.Helpers
alias Makina.Error
alias Makina.Info
##############################################################################
# Type definitions and module docs
##############################################################################
@moduledoc false
@typedoc "Type that represents the options allowed in `#{inspect(__MODULE__)}`"
@type option() :: {:model, module()} | {:where, Keyword.t(atom())} | {:hiding, [atom()]}
##############################################################################
# Exposed macros
##############################################################################
# TODO: check options
@spec __using__([option()]) :: Macro.t()
defmacro __using__(options) do
options = Enum.map(options, fn opt -> Macro.prewalk(opt, &Macro.expand(&1, __CALLER__)) end)
# Ensure a model is passed as an argument and it's available.
module =
if options[:model] do
options[:model]
else
Error.throw_error("missing model to import")
end
ensure_model_loaded!(module)
# Extract state info
state_decl = generate_state(module)
# Extract invariants info
invariants_decl = generate_invariants(module)
# Extract command info
commands_decl = generate_commands(module, options)
quote do
use Makina
unquote(state_decl)
unquote(invariants_decl)
unquote_splicing(commands_decl)
end
end
##############################################################################
# State import
##############################################################################
@spec generate_state(module()) :: Macro.t()
defp generate_state(module) do
%{attributes: attrs, dynamic_types: d_types, symbolic_types: s_types, module: module} =
Info.get_state!(module)
attrs =
for attr <- attrs do
d_t = d_types[attr]
s_t = s_types[attr]
impl = quote do: unquote(module).unquote(attr)()
type = if s_t == d_t, do: d_t, else: quote(do: symbolic(unquote(d_t)))
expr = quote do: unquote(impl) :: unquote(type)
{attr, expr}
end
quote do
state unquote(attrs)
end
end
##############################################################################
# Invariant generate
##############################################################################
@spec generate_invariants(module()) :: Macro.t()
defp generate_invariants(module) do
%{invariants: invs, module: module} = Info.get_invariants!(module)
invs =
for inv <- invs do
state = Macro.var(:state, nil)
{inv, quote(do: unquote(module).unquote(inv)(unquote(state)))}
end
quote do
invariants unquote(invs)
end
end
##############################################################################
# Command generate
##############################################################################
@spec generate_commands(module(), [option()]) :: [Macro.t()]
defp generate_commands(module, options) do
hiding = if options[:hiding], do: options[:hiding], else: []
rename = if options[:where], do: options[:where], else: []
cmds =
Info.get_commands!(module)
|> Enum.filter(&(&1 not in Keyword.keys(rename)))
|> Enum.map(&{&1, &1})
for {old_name, new_name} <- cmds ++ rename,
old_name not in hiding do
%{result: result, arguments: arguments} = Info.get_command!(module, old_name)
arguments =
Enum.map(arguments, fn {arg, type} ->
quote do: unquote(Macro.var(arg, nil)) :: unquote(type)
end)
quote do
command unquote(new_name)(unquote_splicing(arguments)) :: unquote(result),
extends: unquote(module).unquote(old_name)
end
end
end
end