defmodule Tagged do @moduledoc ~S""" Generates definitions of various things related to tagged value tuples, like `{:ok, value}` and `{:error, reason}`. ## Examples defmodule Tagged.Status use Tagged deftagged ok deftagged error end ### Construct and Destructure iex> use Tagged.Status iex> ok(:computer) {:ok, :computer} iex> with error(reason) <- {:ok, :computer}, do: raise reason {:ok, :computer} ### Type definitions _iex> use Tagged.Status _iex> t Tagged.Status.error @type error() :: {:error, term()} Tagged value tuple, containing term(). """ @moduledoc since: "0.1.0" require __MODULE__.Constructor require __MODULE__.Typedef @doc ~S""" Generates a macro that definies all things related to a tagged value tuple, `{atom(), term()}`. By default the macro has the same name as the tag, and all the things are generated. ## Keywords - `as: name` Override default macro name. See `Tagged.Constructor` - `type: false` Override type definition. See `Tagged.Typedef` """ @doc since: "0.1.0" defmacro deftagged(tag, opts \\ []) do block = get_params(tag, Macro.expand_once(opts, __CALLER__), __CALLER__.module) |> generate_parts() quote do: (unquote_splicing(block)) end ############################################################################## ## ## Public API ends here, internal helper functions follows ## ############################################################################## @typep block :: [Macro.t()] @typep macro? :: Macro.t() | false | nil @typep accumulator :: {block(), Keyword.t()} @typep macro_gen :: (Keyword.t() -> macro?()) @doc false @spec get_params(Macro.t(), Keyword.t(), module()) :: Keyword.t() defp get_params(tag, opts, module) do name = Keyword.get(opts, :as, tag) [ name_atom: name |> Macro.to_string() |> String.to_atom(), name_var: name, tag_atom: tag |> Macro.to_string() |> String.to_atom(), opts: opts ++ Module.get_attribute(module, :tagged__using__opts, []) ] end @doc false @spec start(Keyword.t()) :: accumulator() defp start(params), do: {[], params} @doc false @spec finish(accumulator()) :: block() defp finish({acc, _}), do: acc |> Enum.reverse() @doc false @spec accumulate(accumulator(), macro?()) :: accumulator() defp accumulate(acc, result) when result in [nil, false], do: acc defp accumulate({acc, params}, result), do: {[result | acc], params} @doc false @spec pipe(accumulator(), macro_gen()) :: accumulator() defp pipe({_, params} = acc, f), do: accumulate(acc, f.(params)) @doc false @spec generate_parts(Keyword.t()) :: Macro.t() defp generate_parts(params) do start(params) |> pipe(&__MODULE__.Constructor.__deftagged__(&1)) |> pipe(&__MODULE__.Typedef.__deftagged__(&1)) |> finish() end defmacro __using__(opts) do quote do Module.register_attribute(__MODULE__, :tagged__using__opts, []) Module.put_attribute(__MODULE__, :tagged__using__opts, unquote(opts)) import unquote(__MODULE__) defmacro __using__(opts) do quote do: import(unquote(__MODULE__), unquote(opts)) end end end end