1defmodule ExActor.Delegator do
2  @moduledoc """
3  Provides `delegate_to/2` macro that can be used to simplify cases when
4  call/cast operations delegate to another module.
5  """
6
7  @doc """
8  Creates wrapper operations around the `target_module`.
9
10  For example:
11
12      defmodule HashDictServer do
13        use ExActor.GenServer
14        import ExActor.Delegator
15
16        defstart start_link, do: initial_state(HashDict.new)
17
18        delegate_to HashDict do
19          query get/2
20          trans put/3
21        end
22      end
23
24  This is the same as:
25
26      defmodule HashDictServer do
27        use ExActor.GenServer
28
29        defstart start_link, do: initial_state(HashDict.new)
30
31        defcall get(k), state: state do
32          HashDict.get(state, k)
33          |> reply
34        end
35
36        defcast put(k, v), state:state do
37          HashDict.put(state, k, v)
38          |> new_state
39        end
40      end
41  """
42  defmacro delegate_to(target_module, opts) do
43    statements(opts[:do])
44    |> Enum.map(&(parse_instruction(target_module, &1)))
45  end
46
47  defp statements({:__block__, _, statements}), do: statements
48  defp statements(statement), do: [statement]
49
50
51  defp parse_instruction(target_module, {:init, _, _}) do
52    quote do
53      definit do
54        unquote(target_module).new
55        |> initial_state
56      end
57    end
58  end
59
60  defp parse_instruction(target_module, {:query, _, [{:/, _, [{fun, _, _}, arity]}]}) do
61    make_delegate(:defcall, fun, arity,
62      quote do
63        unquote(forward_call(target_module, fun, arity))
64        |> reply
65      end
66    )
67  end
68
69  defp parse_instruction(target_module, {:trans, _, [{:/, _, [{fun, _, _}, arity]}]}) do
70    make_delegate(:defcast, fun, arity,
71      quote do
72        unquote(forward_call(target_module, fun, arity))
73        |> new_state
74      end
75    )
76  end
77
78
79  defp make_delegate(type, fun, arity, code) do
80    quote do
81      unquote(type)(
82        unquote(fun)(unquote_splicing(make_args(arity))),
83        state: state,
84        do: unquote(code)
85      )
86    end
87  end
88
89
90  defp forward_call(target_module, fun, arity) do
91    full_args = [quote(do: state) | make_args(arity)]
92
93    quote do
94      unquote(target_module).unquote(fun)(unquote_splicing(full_args))
95    end
96  end
97
98
99  defp make_args(arity) when arity > 0 do
100    1..arity
101    |> Enum.map(&{:"arg#{&1}", [], nil})
102    |> tl
103  end
104end