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