Use inspect to convert atoms

Use inspect to convert atoms

import Kernel, except: [to_binary: 1]

defmodule Macro do
  @moduledoc """
  This module provides conveniences for working with macros.
  """

  @doc """
  Returns a list of binary operators. This is available
  as a macro so it can be used in guard clauses.
  """
  defmacro binary_ops do
    [
      :===, :!==,
      :==, :!=, :<=, :>=,
      :&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :/>, :=~,
      :<, :>,
      :+, :-, :*, :/, :=, :|, :.,
      :and, :or, :xor, :when, :in, :inlist, :inbits,
      :<<<, :>>>, :|||, :&&&, :^^^, :~~~
    ]
  end

  @doc """
  Returns a list of unary operators. This is available
  as a macro so it can be used in guard clauses.
  """
  defmacro unary_ops do
    [:!, :@, :^, :not, :+, :-]
  end

  @doc """
  Recursively escapes the given value so it can be inserted
  into a syntax tree. Structures that are valid syntax nodes
  (like atoms, integers, binaries) are represented by themselves.

  ## Examples

      Macro.escape(:foo)
      #=> :foo

      Macro.escape({ :a, :b, :c })
      #=> { :{}, 0, [:a, :b, :c] }
  """
  def escape({ left, right }) do
    { escape(left), escape(right) }
  end

  def escape(tuple) when is_tuple(tuple) do
    { :{}, 0, escape(tuple_to_list(tuple)) }
  end

  def escape(list) when is_list(list) do
    lc item inlist list, do: escape(item)
  end

  def escape(other), do: other

  @doc %B"""
  Unescape the given chars. This is the unescaping behavior
  used by default in Elixir single- and double-quoted strings.
  Check `unescape_binary/2` for information on how to customize
  the escaping map.

  In this setup, Elixir will escape the following: `\b`, `\d`,
  `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are also
  escaped according to the latin1 set they represent.

  This function is commonly used on sigil implementations
  (like `%r`, `%b` and others).

  ## Examples

      Macro.unescape_binary "example\\n"
      #=> "example\n"

  In the example above, we pass a string with `\n` escaped
  and we return a version with it unescaped.
  """
  def unescape_binary(chars) do
    :elixir_interpolation.unescape_chars(chars)
  end

  @doc %B"""
  Unescape the given chars according to the map given.
  Check `unescape/1` if you want to use the same map as
  Elixir single- and double-quoted strings.

  ## Map

  The map must be a function. The function receives an integer
  representing the number of the characters it wants to unescape.
  Here is the default mapping function implemented by Elixir:

      def unescape_map(?b), do: ?\b
      def unescape_map(?d), do: ?\d
      def unescape_map(?e), do: ?\e
      def unescape_map(?f), do: ?\f
      def unescape_map(?n), do: ?\n
      def unescape_map(?r), do: ?\r
      def unescape_map(?s), do: ?\s
      def unescape_map(?t), do: ?\t
      def unescape_map(?v), do: ?\v
      def unescape_map(e),  do: e

  If the `unescape_map` function returns false. The char is
  not escaped and `\` is kept in the char list.

  ## Octals

  Octals will by default be escaped unless the map function
  returns false for ?0.

  ## Examples

  Using the unescape_map defined above is easy:

      Macro.unescape_binary "example\\n", unescape_map(&1)

  """
  def unescape_binary(chars, map) do
    :elixir_interpolation.unescape_chars(chars, map)
  end

  @doc """
  Unescape the given tokens according to the default map.
  Check `unescape/1` and `unescape/2` for more information
  about unescaping. Only tokens that are binaries are
  unescaped, all others are ignored. This method is useful
  when implementing your own sigils. Check the implementation
  of `Kernel.__b__` for examples.
  """
  def unescape_tokens(tokens) do
    :elixir_interpolation.unescape_tokens(tokens)
  end

  @doc """
  Unescape the given tokens according to the given map.
  Check `unescape_tokens/1` and `unescaped/2` for more information.
  """
  def unescape_tokens(tokens, map) do
    :elixir_interpolation.unescape_tokens(tokens, map)
  end

  @doc """
  Converts the given expression to a binary.

  ## Examples

      Macro.to_binary(quote do: foo.bar(1, 2, 3))
      #=> "foo.bar(1, 2, 3)"

  """
  def to_binary(tree)

  # Variables
  def to_binary({ var, _, atom }) when is_atom(atom) do
    atom_to_binary(var, :utf8)
  end

  # Aliases
  def to_binary({ :__aliases__, _, refs }) do
    Enum.map_join(refs, ".", call_to_binary(&1))
  end

  # Blocks
  def to_binary({ :__block__, _, [expr] }) do
    to_binary(expr)
  end

  def to_binary({ :__block__, _, _ } = expr) do
    block = adjust_new_lines block_to_binary(expr), "\n  "
    "(\n  " <> block <> "\n)"
  end

  # Bits containers
  def to_binary({ :<<>>, _, args }) do
    "<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
  end

  # Tuple containers
  def to_binary({ :{}, _, args }) do
    "{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
  end

  # List containers
  def to_binary({ :[], _, args }) do
    "[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
  end

  # Fn keyword
  def to_binary({ :fn, _, [[do: block]] }) do
    "fn " <> block_to_binary(block) <> "\nend"
  end

  # Partial call
  def to_binary({ :&, _, [num] }) do
    "&#{num}"
  end

  # Binary ops
  def to_binary({ op, _, [left, right] }) when op in binary_ops do
    op_to_binary(left) <> " #{op} " <> op_to_binary(right)
  end

  # Unary ops
  def to_binary({ op, _, [arg] }) when op in unary_ops do
    atom_to_binary(op, :utf8) <> to_binary(arg)
  end

  # All other calls
  def to_binary({ target, _, args }) when is_list(args) do
    { list, last } = :elixir_tree_helpers.split_last(args)
    case is_kw_blocks?(last) do
      true  -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
      false -> call_to_binary_with_args(target, args)
    end
  end

  # Two-item tuples
  def to_binary({ left, right }) do
    to_binary({ :{}, 0, [left, right] })
  end

  # Lists
  def to_binary(list) when is_list(list) do
    to_binary({ :[], 0, list })
  end

  # All other structures
  def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)

  # Block keywords
  defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]

  defp is_kw_blocks?([_|_] = kw) do
    Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
  end
  defp is_kw_blocks?(_), do: false

  defp module_to_binary(atom) when is_atom(atom) do
    inspect(atom)
  end
  defp module_to_binary(other), do: call_to_binary(other)

  defp call_to_binary(atom) when is_atom(atom),  do: atom_to_binary(atom, :utf8)
  defp call_to_binary({ :., _, [arg] }),         do: module_to_binary(arg) <> "."
  defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
  defp call_to_binary(other),                    do: to_binary(other)

  defp call_to_binary_with_args(target, args) do
    args = Enum.map_join(args, ", ", to_binary(&1))
    call_to_binary(target) <> "(" <> args <> ")"
  end

  defp kw_blocks_to_binary(kw) do
    Enum.reduce(kw_keywords, " ", fn(x, acc) ->
      case Keyword.has_key?(kw, x) do
        true  -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
        false -> acc
      end
    end) <> "end"
  end

  defp kw_block_to_binary(key, value) do
    block = adjust_new_lines block_to_binary(value), "\n  "
    atom_to_binary(key, :utf8) <> "\n  " <> block <> "\n"
  end

  defp block_to_binary({ :->, _, exprs }) do
    Enum.map_join(exprs, "\n", fn({ left, right }) ->
      left = Enum.map_join(left, ", ", to_binary(&1))
      left <> " ->\n  " <> adjust_new_lines block_to_binary(right), "\n  "
    end)
  end

  defp block_to_binary({ :__block__, _, exprs }) do
    Enum.map_join(exprs, "\n", to_binary(&1))
  end

  defp block_to_binary(other), do: to_binary(other)

  defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
    "(" <> to_binary(expr) <> ")"
  end

  defp op_to_binary(expr), do: to_binary(expr)

  defp adjust_new_lines(block, replacement) do
    bc <<x>> inbits block do
      << case x == ?\n do
        true  -> replacement
        false -> <<x>>
      end :: binary >>
    end
  end

  @doc """
  Receives an expression representation and expands it. The following
  contents are expanded:

  * Macros (local or remote);
  * Aliases are expanded (if possible) and return atoms;
  * All pseudo-variables (__FILE__, __MODULE__, etc);

  In case the expression cannot be expanded, it returns the expression itself.

  Notice that `Macro.expand` is not recursive and it does not
  expand child expressions. For example, `!some_macro` will expand as:

      iex> IO.puts Macro.to_binary Macro.expand(quote(do: !some_macro), __ENV__)
      case some_macro do
        false -> true
        nil   -> true
        _     -> false
      end

  Notice that the `!` operator is a macro that expands to a case.
  Even though `some_macro` is also a macro, it is not expanded
  because it is a child expression given to `!` as argument.

  ## Examples

  In the example below, we have a macro that generates a module
  with a function named `name_length` that returns the length
  of the module name. The value of this function will be calculated
  at compilation time and not at runtime.

  Consider the implementation below:

      defmacro defmodule_with_length(name, do: block) do
        length = length(atom_to_list(name))

        quote do
          defmodule unquote(name) do
            def name_length, do: unquote(length)
            unquote(block)
          end
        end
      end

  When invoked like this:

      defmodule_with_length My.Module do
        def other_function, do: ...
      end

  The compilation will fail because `My.Module` when quoted
  is not an atom, but a syntax tree as follow:

      {:__aliases__, 0, [:My, :Module] }

  That said, we need to expand the aliases node above to an
  atom, so we can retrieve its length. Expanding the node is
  not straight-forward because we also need to expand the
  caller aliases. For example:

      alias MyHelpers, as: My

      defmodule_with_length My.Module do
        def other_function, do: ...
      end

  The final module name will be `MyHelpers.Module` and not
  `My.Module`. With `Macro.expand`, such aliases are taken
  into consideration. Local and remote macros are also
  expanded. We could rewrite our macro above to use this
  function as:

      defmacro defmodule_with_length(name, do: block) do
        expanded = Macro.expand(name, __CALLER__)
        length   = length(atom_to_list(expanded))

        quote do
          defmodule unquote(name) do
            def name_length, do: unquote(length)
            unquote(block)
          end
        end
      end

  """
  def expand(aliases, env)

  # The first case we handle is __aliases__. In case
  # aliases just contain one item, we are sure it is
  # an atom, so we just expand it based on the aliases
  # dict.
  def expand({ :__aliases__, _, [h] }, env) when h != Elixir do
    expand_alias(h, env)
  end

  # In case aliases contains more than one item, we need
  # to loop them checking if they are all atoms or not.
  # Macros and pseudo-variables are then expanded.
  def expand({ :__aliases__, _, [h|t] } = original, env) do
    aliases = case h do
      x when is_atom(x) and x != Elixir -> [expand_alias(x, env)|t]
      _                                 -> [h|t]
    end

    aliases = lc alias inlist aliases, do: expand(alias, env)

    case :lists.all(is_atom(&1), aliases) do
      true  -> :elixir_aliases.concat(aliases)
      false -> original
    end
  end

  # Expand @ calls
  def expand({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
    case (module = env.module) && Module.open?(module) do
      true  -> Module.get_attribute(module, name)
      false -> original
    end
  end

  # Expand pseudo-variables
  def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
  def expand({ :__FILE__, _, atom }, env)   when is_atom(atom), do: env.file
  def expand({ :__ENV__, _, atom }, env)    when is_atom(atom), do: env

  # Expand possible macro import invocation
  def expand({ atom, line, args } = original, env) when is_atom(atom) do
    args = case is_atom(args) do
      true  -> []
      false -> args
    end

    case not is_partial?(args) do
      false -> original
      true  ->
        expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
          env.module, env.function, env.requires, env.macros, env)
        case expand do
          { :ok, _, expanded } -> expanded
          { :error, _ }     -> original
        end
    end
  end

  # Expand possible macro require invocation
  def expand({ { :., _, [left, right] }, line, args } = original, env) when is_atom(right) do
    receiver = expand(left, env)

    case is_atom(receiver) and not is_partial?(args) do
      false -> original
      true  ->
        expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
          args, env.module, env.function, env.requires, env)
        case expand do
          { :ok, expanded } -> expanded
          { :error, _ }     -> original
        end
    end
  end

  # Anything else is just returned
  def expand(other, _env), do: other

  ## Helpers

  defp is_partial?(args) do
    :lists.any(match?({ :&, _, [_] }, &1), args)
  end

  defp expand_alias(h, env) do
    atom = list_to_atom('Elixir-' ++ atom_to_list(h))
    :elixir_aliases.lookup(atom, env.aliases)
  end

  @doc """
  Recurs the quoted expression checking if all sub terms are
  safe (i.e. they represented data structured and don't actually
  evaluate code) and returns `:ok` unless a given term is unsafe,
  which is returned as `{ :unsafe, term }`.
  """
  def safe_term(terms) do
    do_safe_term(terms) || :ok
  end

  def do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
    do_safe_term(terms)
  end

  def do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
    do_safe_term(term)
  end

  def do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
  def do_safe_term(terms) when is_list(terms),  do: Enum.find_value(terms, do_safe_term(&1))
  def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
  def do_safe_term(_), do: nil
end

import Kernel, except: [to_binary: 1]

defmodule Macro do
  @moduledoc """
  This module provides conveniences for working with macros.
  """

  @doc """
  Returns a list of binary operators. This is available
  as a macro so it can be used in guard clauses.
  """
  defmacro binary_ops do
    [
      :===, :!==,
      :==, :!=, :<=, :>=,
      :&&, :||, :<>, :++, :--, :**, ://, :::, :<-, :.., :/>, :=~,
      :<, :>,
      :+, :-, :*, :/, :=, :|, :.,
      :and, :or, :xor, :when, :in, :inlist, :inbits,
      :<<<, :>>>, :|||, :&&&, :^^^, :~~~
    ]
  end

  @doc """
  Returns a list of unary operators. This is available
  as a macro so it can be used in guard clauses.
  """
  defmacro unary_ops do
    [:!, :@, :^, :not, :+, :-]
  end

  @doc """
  Recursively escapes the given value so it can be inserted
  into a syntax tree. Structures that are valid syntax nodes
  (like atoms, integers, binaries) are represented by themselves.

  ## Examples

      Macro.escape(:foo)
      #=> :foo

      Macro.escape({ :a, :b, :c })
      #=> { :{}, 0, [:a, :b, :c] }
  """
  def escape({ left, right }) do
    { escape(left), escape(right) }
  end

  def escape(tuple) when is_tuple(tuple) do
    { :{}, 0, escape(tuple_to_list(tuple)) }
  end

  def escape(list) when is_list(list) do
    lc item inlist list, do: escape(item)
  end

  def escape(other), do: other

  @doc %B"""
  Unescape the given chars. This is the unescaping behavior
  used by default in Elixir single- and double-quoted strings.
  Check `unescape_binary/2` for information on how to customize
  the escaping map.

  In this setup, Elixir will escape the following: `\b`, `\d`,
  `\e`, `\f`, `\n`, `\r`, `\s`, `\t` and `\v`. Octals are also
  escaped according to the latin1 set they represent.

  This function is commonly used on sigil implementations
  (like `%r`, `%b` and others).

  ## Examples

      Macro.unescape_binary "example\\n"
      #=> "example\n"

  In the example above, we pass a string with `\n` escaped
  and we return a version with it unescaped.
  """
  def unescape_binary(chars) do
    :elixir_interpolation.unescape_chars(chars)
  end

  @doc %B"""
  Unescape the given chars according to the map given.
  Check `unescape/1` if you want to use the same map as
  Elixir single- and double-quoted strings.

  ## Map

  The map must be a function. The function receives an integer
  representing the number of the characters it wants to unescape.
  Here is the default mapping function implemented by Elixir:

      def unescape_map(?b), do: ?\b
      def unescape_map(?d), do: ?\d
      def unescape_map(?e), do: ?\e
      def unescape_map(?f), do: ?\f
      def unescape_map(?n), do: ?\n
      def unescape_map(?r), do: ?\r
      def unescape_map(?s), do: ?\s
      def unescape_map(?t), do: ?\t
      def unescape_map(?v), do: ?\v
      def unescape_map(e),  do: e

  If the `unescape_map` function returns false. The char is
  not escaped and `\` is kept in the char list.

  ## Octals

  Octals will by default be escaped unless the map function
  returns false for ?0.

  ## Examples

  Using the unescape_map defined above is easy:

      Macro.unescape_binary "example\\n", unescape_map(&1)

  """
  def unescape_binary(chars, map) do
    :elixir_interpolation.unescape_chars(chars, map)
  end

  @doc """
  Unescape the given tokens according to the default map.
  Check `unescape/1` and `unescape/2` for more information
  about unescaping. Only tokens that are binaries are
  unescaped, all others are ignored. This method is useful
  when implementing your own sigils. Check the implementation
  of `Kernel.__b__` for examples.
  """
  def unescape_tokens(tokens) do
    :elixir_interpolation.unescape_tokens(tokens)
  end

  @doc """
  Unescape the given tokens according to the given map.
  Check `unescape_tokens/1` and `unescaped/2` for more information.
  """
  def unescape_tokens(tokens, map) do
    :elixir_interpolation.unescape_tokens(tokens, map)
  end

  @doc """
  Converts the given expression to a binary.

  ## Examples

      Macro.to_binary(quote do: foo.bar(1, 2, 3))
      #=> "foo.bar(1, 2, 3)"

  """
  def to_binary(tree)

  # Variables
  def to_binary({ var, _, atom }) when is_atom(atom) do
    atom_to_binary(var, :utf8)
  end

  # Aliases
  def to_binary({ :__aliases__, _, refs }) do
    Enum.map_join(refs, ".", call_to_binary(&1))
  end

  # Blocks
  def to_binary({ :__block__, _, [expr] }) do
    to_binary(expr)
  end

  def to_binary({ :__block__, _, _ } = expr) do
    block = adjust_new_lines block_to_binary(expr), "\n  "
    "(\n  " <> block <> "\n)"
  end

  # Bits containers
  def to_binary({ :<<>>, _, args }) do
    "<<" <> Enum.map_join(args, ", ", to_binary(&1)) <> ">>"
  end

  # Tuple containers
  def to_binary({ :{}, _, args }) do
    "{" <> Enum.map_join(args, ", ", to_binary(&1)) <> "}"
  end

  # List containers
  def to_binary({ :[], _, args }) do
    "[" <> Enum.map_join(args, ", ", to_binary(&1)) <> "]"
  end

  # Fn keyword
  def to_binary({ :fn, _, [[do: block]] }) do
    "fn " <> block_to_binary(block) <> "\nend"
  end

  # Partial call
  def to_binary({ :&, _, [num] }) do
    "&#{num}"
  end

  # Binary ops
  def to_binary({ op, _, [left, right] }) when op in binary_ops do
    op_to_binary(left) <> " #{op} " <> op_to_binary(right)
  end

  # Unary ops
  def to_binary({ op, _, [arg] }) when op in unary_ops do
    atom_to_binary(op, :utf8) <> to_binary(arg)
  end

  # All other calls
  def to_binary({ target, _, args }) when is_list(args) do
    { list, last } = :elixir_tree_helpers.split_last(args)
    case is_kw_blocks?(last) do
      true  -> call_to_binary_with_args(target, list) <> kw_blocks_to_binary(last)
      false -> call_to_binary_with_args(target, args)
    end
  end

  # Two-item tuples
  def to_binary({ left, right }) do
    to_binary({ :{}, 0, [left, right] })
  end

  # Lists
  def to_binary(list) when is_list(list) do
    to_binary({ :[], 0, list })
  end

  # All other structures
  def to_binary(other), do: Binary.Inspect.inspect(other, raw: true)

  # Block keywords
  defmacrop kw_keywords, do: [:do, :catch, :rescue, :after, :else]

  defp is_kw_blocks?([_|_] = kw) do
    Enum.all?(kw, match?({x, _} when x in kw_keywords, &1))
  end
  defp is_kw_blocks?(_),          do: false

  defp module_to_binary(atom) when is_atom(atom) do
    b = atom_to_binary(atom, :utf8)
    first = String.at(b, 0)
    if String.upcase(first) == first do # "big" atom"
      b
    else # regular atom
      ":" <> b
    end
  end
  defp module_to_binary(other), do: call_to_binary(other)

  defp call_to_binary(atom) when is_atom(atom),  do: atom_to_binary(atom, :utf8)
  defp call_to_binary({ :., _, [arg] }),         do: module_to_binary(arg) <> "."
  defp call_to_binary({ :., _, [left, right] }), do: module_to_binary(left) <> "." <> call_to_binary(right)
  defp call_to_binary(other),                    do: to_binary(other)

  defp call_to_binary_with_args(target, args) do
    args = Enum.map_join(args, ", ", to_binary(&1))
    call_to_binary(target) <> "(" <> args <> ")"
  end

  defp kw_blocks_to_binary(kw) do
    Enum.reduce(kw_keywords, " ", fn(x, acc) ->
      case Keyword.has_key?(kw, x) do
        true  -> acc <> kw_block_to_binary(x, Keyword.get(kw, x))
        false -> acc
      end
    end) <> "end"
  end

  defp kw_block_to_binary(key, value) do
    block = adjust_new_lines block_to_binary(value), "\n  "
    atom_to_binary(key, :utf8) <> "\n  " <> block <> "\n"
  end

  defp block_to_binary({ :->, _, exprs }) do
    Enum.map_join(exprs, "\n", fn({ left, right }) ->
      left = Enum.map_join(left, ", ", to_binary(&1))
      left <> " ->\n  " <> adjust_new_lines block_to_binary(right), "\n  "
    end)
  end

  defp block_to_binary({ :__block__, _, exprs }) do
    Enum.map_join(exprs, "\n", to_binary(&1))
  end

  defp block_to_binary(other), do: to_binary(other)

  defp op_to_binary({ op, _, [_, _] } = expr) when op in binary_ops do
    "(" <> to_binary(expr) <> ")"
  end

  defp op_to_binary(expr), do: to_binary(expr)

  defp adjust_new_lines(block, replacement) do
    bc <<x>> inbits block do
      << case x == ?\n do
        true  -> replacement
        false -> <<x>>
      end :: binary >>
    end
  end

  @doc """
  Receives an expression representation and expands it. The following
  contents are expanded:

  * Macros (local or remote);
  * Aliases are expanded (if possible) and return atoms;
  * All pseudo-variables (__FILE__, __MODULE__, etc);

  In case the expression cannot be expanded, it returns the expression itself.

  Notice that `Macro.expand` is not recursive and it does not
  expand child expressions. For example, `!some_macro` will expand as:

      iex> IO.puts Macro.to_binary Macro.expand(quote(do: !some_macro), __ENV__)
      case some_macro do
        false -> true
        nil   -> true
        _     -> false
      end

  Notice that the `!` operator is a macro that expands to a case.
  Even though `some_macro` is also a macro, it is not expanded
  because it is a child expression given to `!` as argument.

  ## Examples

  In the example below, we have a macro that generates a module
  with a function named `name_length` that returns the length
  of the module name. The value of this function will be calculated
  at compilation time and not at runtime.

  Consider the implementation below:

      defmacro defmodule_with_length(name, do: block) do
        length = length(atom_to_list(name))

        quote do
          defmodule unquote(name) do
            def name_length, do: unquote(length)
            unquote(block)
          end
        end
      end

  When invoked like this:

      defmodule_with_length My.Module do
        def other_function, do: ...
      end

  The compilation will fail because `My.Module` when quoted
  is not an atom, but a syntax tree as follow:

      {:__aliases__, 0, [:My, :Module] }

  That said, we need to expand the aliases node above to an
  atom, so we can retrieve its length. Expanding the node is
  not straight-forward because we also need to expand the
  caller aliases. For example:

      alias MyHelpers, as: My

      defmodule_with_length My.Module do
        def other_function, do: ...
      end

  The final module name will be `MyHelpers.Module` and not
  `My.Module`. With `Macro.expand`, such aliases are taken
  into consideration. Local and remote macros are also
  expanded. We could rewrite our macro above to use this
  function as:

      defmacro defmodule_with_length(name, do: block) do
        expanded = Macro.expand(name, __CALLER__)
        length   = length(atom_to_list(expanded))

        quote do
          defmodule unquote(name) do
            def name_length, do: unquote(length)
            unquote(block)
          end
        end
      end

  """
  def expand(aliases, env)

  # The first case we handle is __aliases__. In case
  # aliases just contain one item, we are sure it is
  # an atom, so we just expand it based on the aliases
  # dict.
  def expand({ :__aliases__, _, [h] }, env) when h != Elixir do
    expand_alias(h, env)
  end

  # In case aliases contains more than one item, we need
  # to loop them checking if they are all atoms or not.
  # Macros and pseudo-variables are then expanded.
  def expand({ :__aliases__, _, [h|t] } = original, env) do
    aliases = case h do
      x when is_atom(x) and x != Elixir -> [expand_alias(x, env)|t]
      _                                 -> [h|t]
    end

    aliases = lc alias inlist aliases, do: expand(alias, env)

    case :lists.all(is_atom(&1), aliases) do
      true  -> :elixir_aliases.concat(aliases)
      false -> original
    end
  end

  # Expand @ calls
  def expand({ :@, _, [{ name, _, args }] } = original, env) when is_atom(args) or args == [] do
    case (module = env.module) && Module.open?(module) do
      true  -> Module.get_attribute(module, name)
      false -> original
    end
  end

  # Expand pseudo-variables
  def expand({ :__MODULE__, _, atom }, env) when is_atom(atom), do: env.module
  def expand({ :__FILE__, _, atom }, env)   when is_atom(atom), do: env.file
  def expand({ :__ENV__, _, atom }, env)    when is_atom(atom), do: env

  # Expand possible macro import invocation
  def expand({ atom, line, args } = original, env) when is_atom(atom) do
    args = case is_atom(args) do
      true  -> []
      false -> args
    end

    case not is_partial?(args) do
      false -> original
      true  ->
        expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
          env.module, env.function, env.requires, env.macros, env)
        case expand do
          { :ok, _, expanded } -> expanded
          { :error, _ }     -> original
        end
    end
  end

  # Expand possible macro require invocation
  def expand({ { :., _, [left, right] }, line, args } = original, env) when is_atom(right) do
    receiver = expand(left, env)

    case is_atom(receiver) and not is_partial?(args) do
      false -> original
      true  ->
        expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
          args, env.module, env.function, env.requires, env)
        case expand do
          { :ok, expanded } -> expanded
          { :error, _ }     -> original
        end
    end
  end

  # Anything else is just returned
  def expand(other, _env), do: other

  ## Helpers

  defp is_partial?(args) do
    :lists.any(match?({ :&, _, [_] }, &1), args)
  end

  defp expand_alias(h, env) do
    atom = list_to_atom('Elixir-' ++ atom_to_list(h))
    :elixir_aliases.lookup(atom, env.aliases)
  end

  @doc """
  Recurs the quoted expression checking if all sub terms are
  safe (i.e. they represented data structured and don't actually
  evaluate code) and returns `:ok` unless a given term is unsafe,
  which is returned as `{ :unsafe, term }`.
  """
  def safe_term(terms) do
    do_safe_term(terms) || :ok
  end

  def do_safe_term({ local, _, terms }) when local in [:{}, :[], :__aliases__] do
    do_safe_term(terms)
  end

  def do_safe_term({ unary, _, [term] }) when unary in [:+, :-] do
    do_safe_term(term)
  end

  def do_safe_term({ left, right }), do: do_safe_term(left) || do_safe_term(right)
  def do_safe_term(terms) when is_list(terms),  do: Enum.find_value(terms, do_safe_term(&1))
  def do_safe_term(terms) when is_tuple(terms), do: { :unsafe, terms }
  def do_safe_term(_), do: nil
end