Packages
igniter
0.2.8
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.30
0.6.29
0.6.28
0.6.27
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.52
0.5.51
0.5.50
0.5.49
0.5.48
0.5.47
0.5.46
0.5.45
0.5.44
0.5.43
0.5.42
0.5.41
0.5.40
0.5.39
0.5.38
0.5.37
0.5.36
0.5.35
0.5.34
0.5.33
0.5.32
0.5.31
0.5.30
0.5.29
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.78
0.3.77
0.3.76
0.3.75
0.3.74
0.3.73
0.3.72
0.3.71
0.3.70
0.3.69
0.3.68
0.3.67
0.3.66
0.3.65
0.3.64
0.3.63
0.3.62
0.3.61
0.3.60
0.3.59
0.3.58
0.3.57
0.3.56
0.3.55
0.3.54
0.3.53
0.3.52
0.3.51
0.3.50
0.3.49
0.3.48
0.3.47
0.3.46
0.3.45
0.3.44
0.3.43
0.3.42
0.3.41
0.3.40
0.3.39
0.3.38
0.3.37
0.3.36
0.3.35
0.3.34
0.3.33
0.3.32
0.3.31
0.3.30
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A code generation and project patching framework
Current section
Files
Jump to
Current section
Files
lib/igniter/code/function.ex
defmodule Igniter.Code.Function do
@moduledoc """
Utilities for working with functions.
"""
alias Igniter.Code.Common
alias Sourceror.Zipper
@doc "Returns `true` if the argument at the provided index exists and matches the provided pattern"
defmacro argument_matches_pattern?(zipper, index, pattern) do
quote do
Igniter.Code.Function.argument_matches_predicate?(
unquote(zipper),
unquote(index),
fn zipper ->
code_at_node =
zipper
|> Sourceror.Zipper.subtree()
|> Sourceror.Zipper.root()
match?(unquote(pattern), code_at_node)
end
)
end
end
@doc "Moves to a function call by the given name and arity, matching the given predicate, in the current scope"
@spec move_to_function_call_in_current_scope(Zipper.t(), atom, non_neg_integer()) ::
{:ok, Zipper.t()} | :error
def move_to_function_call_in_current_scope(zipper, name, arity, predicate \\ fn _ -> true end)
def move_to_function_call_in_current_scope(zipper, name, [arity | arities], predicate) do
case move_to_function_call_in_current_scope(zipper, name, arity, predicate) do
:error ->
move_to_function_call_in_current_scope(zipper, name, arities, predicate)
{:ok, zipper} ->
{:ok, zipper}
end
end
def move_to_function_call_in_current_scope(_, _, [], _) do
:error
end
def move_to_function_call_in_current_scope(%Zipper{} = zipper, name, arity, predicate) do
if function_call?(zipper, name, arity) && predicate.(zipper) do
{:ok, zipper}
else
Common.move_right(zipper, fn zipper ->
function_call?(zipper, name, arity) && predicate.(zipper)
end)
end
end
@doc "Moves to a function call by the given name and arity, matching the given predicate, in the current or lower scope"
@spec move_to_function_call(Zipper.t(), atom, non_neg_integer()) ::
{:ok, Zipper.t()} | :error
def move_to_function_call(zipper, name, arity, predicate \\ fn _ -> true end)
def move_to_function_call(zipper, name, [arity | arities], predicate) do
case move_to_function_call(zipper, name, arity, predicate) do
:error ->
move_to_function_call(zipper, name, arities, predicate)
{:ok, zipper} ->
{:ok, zipper}
end
end
def move_to_function_call(_, _, [], _) do
:error
end
def move_to_function_call(%Zipper{} = zipper, name, arity, predicate) do
if function_call?(zipper, name, arity) && predicate.(zipper) do
{:ok, zipper}
else
Common.move_next(zipper, fn zipper ->
function_call?(zipper, name, arity) && predicate.(zipper)
end)
end
end
@doc "Returns `true` if the node is a function call of the given name and arity"
@spec function_call?(Zipper.t(), atom, non_neg_integer()) :: boolean()
def function_call?(%Zipper{} = zipper, name, arity) do
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.subtree()
|> Zipper.root()
|> case do
{^name, _, args} ->
Enum.count(args) == arity
{{^name, _, context}, _, args} when is_atom(context) ->
Enum.count(args) == arity
{:|>, _, [{^name, _, context} | rest]} when is_atom(context) ->
Enum.count(rest) == arity - 1
{:|>, _, [^name | rest]} ->
Enum.count(rest) == arity - 1
_ ->
false
end
end
@doc "Returns `true` if the node is a function call of the given name"
@spec function_call?(Zipper.t(), atom) :: boolean()
def function_call?(%Zipper{} = zipper, name) do
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.subtree()
|> Zipper.root()
|> case do
{^name, _, _} ->
true
{{^name, _, context}, _, _} when is_atom(context) ->
true
{:|>, _, [{^name, _, context} | _rest]} when is_atom(context) ->
true
{:|>, _, [^name | _rest]} ->
true
_ ->
false
end
end
@doc "Returns `true` if the node is a function call"
@spec function_call?(Zipper.t()) :: boolean()
def function_call?(%Zipper{} = zipper) do
zipper
|> Common.maybe_move_to_single_child_block()
|> Zipper.subtree()
|> Zipper.root()
|> case do
{:|>, _, [{name, _, context} | _rest]} when is_atom(context) and is_atom(name) ->
true
{:|>, _, [name | _rest]} when is_atom(name) ->
true
{name, _, _} when is_atom(name) ->
true
{{name, _, context}, _, _} when is_atom(context) and is_atom(name) ->
true
_ ->
false
end
end
@doc "Updates the `nth` argument of a function call, leaving the zipper at the function call's node."
@spec update_nth_argument(
Zipper.t(),
non_neg_integer(),
(Zipper.t() ->
{:ok, Zipper.t()} | :error)
) ::
{:ok, Zipper.t()} | :error
def update_nth_argument(zipper, index, func) do
Common.within(zipper, fn zipper ->
if pipeline?(zipper) do
if index == 0 do
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
func.(zipper)
end
else
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Zipper.rightmost()
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Common.nth_right(index)
|> case do
:error ->
:error
{:ok, nth} ->
func.(nth)
end
end
end
end
else
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Common.nth_right(index)
|> case do
:error ->
:error
{:ok, nth} ->
func.(nth)
end
end
end
end)
end
@doc "Moves to the `nth` argument of a function call."
@spec move_to_nth_argument(
Zipper.t(),
non_neg_integer()
) ::
{:ok, Zipper.t()} | :error
def move_to_nth_argument(zipper, index) do
if function_call?(zipper) do
if pipeline?(zipper) do
if index == 0 do
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
{:ok, zipper}
end
else
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Zipper.rightmost()
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Common.nth_right(index)
|> case do
:error ->
:error
{:ok, nth} ->
{:ok, nth}
end
end
end
end
else
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
zipper
|> Common.nth_right(index)
|> case do
:error ->
:error
{:ok, nth} ->
{:ok, nth}
end
end
end
else
:error
end
end
@doc "Appends an argument to a function call, leaving the zipper at the function call's node."
@spec append_argument(Zipper.t(), any()) :: {:ok, Zipper.t()} | :error
def append_argument(zipper, value) do
if function_call?(zipper) do
if pipeline?(zipper) do
zipper
|> Zipper.down()
|> case do
nil ->
:error
zipper ->
{:ok, Zipper.append_child(zipper, value)}
end
else
{:ok, Zipper.append_child(zipper, value)}
end
else
:error
end
end
@doc "Returns true if the argument at the given index matches the provided predicate"
@spec argument_matches_predicate?(Zipper.t(), non_neg_integer(), (Zipper.t() -> boolean)) ::
boolean()
def argument_matches_predicate?(zipper, index, func) do
if function_call?(zipper) do
if pipeline?(zipper) do
if index == 0 do
zipper
|> Zipper.down()
|> case do
nil -> nil
zipper -> func.(zipper)
end
else
zipper
|> Zipper.down()
|> case do
nil ->
nil
zipper ->
zipper
|> Zipper.rightmost()
|> Zipper.down()
|> case do
nil ->
nil
zipper ->
zipper
|> Common.nth_right(index - 1)
|> case do
:error ->
false
{:ok, zipper} ->
zipper
|> Common.maybe_move_to_single_child_block()
|> func.()
end
end
end
end
else
zipper
|> Zipper.down()
|> case do
nil ->
false
zipper ->
zipper
|> Common.nth_right(index)
|> case do
:error ->
false
{:ok, zipper} ->
zipper
|> Common.maybe_move_to_single_child_block()
|> func.()
end
end
end
else
:error
end
end
defp pipeline?(zipper) do
zipper
|> Zipper.subtree()
|> Zipper.root()
|> case do
{:|>, _, _} -> true
_ -> false
end
end
end