import Kernel, except: [to_string: 1] defmodule Macro do defp adjust_new_lines(block, replacement) do for <>, into: "" do case x == ?\n do true -> replacement false -> <> end end end @doc """ Receives an AST node and expands it once. """ def expand_once(ast, env) do elem(do_expand_once(ast, env), 0) end defp do_expand_once({:__aliases__, _, _} = original, env) do case :elixir_aliases.expand(original, env.aliases, env.macro_aliases, env.lexical_tracker) do receiver when is_atom(receiver) -> :elixir_lexical.record_remote(receiver, env.function, env.lexical_tracker) {receiver, true} aliases -> aliases = :lists.map(&elem(do_expand_once(&1, env), 0), aliases) case :lists.all(&is_atom/1, aliases) do true -> receiver = :elixir_aliases.concat(aliases) :elixir_lexical.record_remote(receiver, env.function, env.lexical_tracker) {receiver, true} false -> {original, false} end end end # Expand compilation environment macros defp do_expand_once({:__MODULE__, _, atom}, env) when is_atom(atom), do: {env.module, true} defp do_expand_once({:__DIR__, _, atom}, env) when is_atom(atom), do: {:filename.dirname(env.file), true} defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom), do: {{:%{}, [], Map.to_list(env)}, true} defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env) when is_atom(atom) and is_atom(field) do if Map.has_key?(env, field) do {Map.get(env, field), true} else {original, false} end end # Expand possible macro import invocation defp do_expand_once({atom, meta, context} = original, env) when is_atom(atom) and is_list(meta) and is_atom(context) do if Macro.Env.has_var?(env, {atom, Keyword.get(meta, :counter, context)}) do {original, false} else case do_expand_once({atom, meta, []}, env) do {_, true} = exp -> exp {_, false} -> {original, false} end end end defp do_expand_once({atom, meta, args} = original, env) when is_atom(atom) and is_list(args) and is_list(meta) do arity = length(args) if special_form?(atom, arity) do {original, false} else module = env.module extra = if function_exported?(module, :__info__, 1) do [{module, module.__info__(:macros)}] else [] end expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args, env, extra, true) case expand do {:ok, receiver, quoted} -> next = :erlang.unique_integer() {:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true} {:ok, _receiver, _name, _args} -> {original, false} :error -> {original, false} end end end # Expand possible macro require invocation defp do_expand_once({{:., _, [left, right]}, meta, args} = original, env) when is_atom(right) do {receiver, _} = do_expand_once(left, env) case is_atom(receiver) do false -> {original, false} true -> expand = :elixir_dispatch.expand_require(meta, receiver, {right, length(args)}, args, env) case expand do {:ok, receiver, quoted} -> next = :erlang.unique_integer() {:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true} :error -> {original, false} end end end # Anything else is just returned defp do_expand_once(other, _env), do: {other, false} @doc """ Returns `true` if the given name and arity is a special form. """ @doc since: "1.7.0" @spec special_form?(name :: atom(), arity()) :: boolean() def special_form?(name, arity) when is_atom(name) and is_integer(arity) do :elixir_import.special_form(name, arity) end @doc """ Returns `true` if the given name and arity is an operator. """ @doc since: "1.7.0" @spec operator?(name :: atom(), arity()) :: boolean() def operator?(name, 2) when is_atom(name), do: Identifier.binary_op(name) != :error def operator?(name, 1) when is_atom(name), do: Identifier.unary_op(name) != :error def operator?(name, arity) when is_atom(name) and is_integer(arity), do: false @doc """ Returns `true` if the given quoted expression is an AST literal. """ @doc since: "1.7.0" @spec quoted_literal?(literal) :: true @spec quoted_literal?(expr) :: false def quoted_literal?(term) def quoted_literal?({left, right}), do: quoted_literal?(left) and quoted_literal?(right) def quoted_literal?(list) when is_list(list), do: Enum.all?(list, "ed_literal?/1) def quoted_literal?(term) do is_atom(term) or is_number(term) or is_binary(term) or is_function(term) end end