Current section
Files
Jump to
Current section
Files
lib/pathex/builder/updaters/force_updater.ex
defmodule Pathex.Builder.ForceUpdater do
# Forceupdater-builder which builds function for updates in given path
@moduledoc false
alias Pathex.Common
import Common, only: [is_var: 1]
import Pathex.Builder.Setter, except: [create_updater: 3]
@behaviour Pathex.Builder
@structure_variable {:x, [], Elixir}
@default_variable {:default, [], Elixir}
@function_variable {:function, [], Elixir}
@doc """
Returns three argument code structure
"""
@impl Pathex.Builder
def build(combination) do
combination
|> Enum.reverse()
|> Enum.reduce(initial(), &reduce_into/2)
|> elem(0)
|> wrap_to_code([@structure_variable, @function_variable, @default_variable])
end
defp reduce_into([path_item | _] = path_items, {acc_code, acc_items}) do
fallback = fallback_from_acc(path_items, acc_items)
setters = Enum.flat_map(path_items, &create_updater(&1, acc_code, acc_items))
acc_items = add_to_acc(path_item, acc_items)
{Common.to_case(setters ++ fallback ++ absolute_fallback()), acc_items}
end
defp initial do
setfunc =
quote do
unquote(@function_variable).()
|> case do
{:ok, value} -> value
:error -> throw(:path_not_found)
end
end
{setfunc, @default_variable}
end
defp create_updater({:keyword, key}, tail, default) when is_var(key) do
quote do
keyword when is_list(keyword) and is_atom(unquote(key)) ->
Keyword.update(keyword, unquote(key), unquote(default), fn val ->
val |> unquote(tail)
end)
end
end
defp create_updater({:keyword, key}, tail, default) when is_atom(key) do
quote do
keyword when is_list(keyword) ->
Keyword.update(keyword, unquote(key), unquote(default), fn val ->
val |> unquote(tail)
end)
end
end
defp create_updater({:list, -1}, _tail, acc_items) do
quote do
list when is_list(list) ->
[unquote(acc_items) | list]
end
end
defp create_updater({:list, index}, tail, acc_items) when is_var(index) do
quote do
list when is_list(list) and unquote(index) == -1 ->
[unquote(acc_items) | list]
list when is_list(list) and is_integer(unquote(index)) and unquote(index) < 0 ->
length = length(list)
if -unquote(index) > length do
len = max(-unquote(index) - 1 - length, 0)
[unquote(acc_items) | List.duplicate(nil, len)] ++ list
else
List.update_at(list, unquote(index), fn x -> x |> unquote(tail) end)
end
list when is_list(list) and is_integer(unquote(index)) ->
length = length(list)
if unquote(index) > length do
len = max(unquote(index) - length, 0)
list ++ List.duplicate(nil, len) ++ [unquote(acc_items)]
else
List.update_at(list, unquote(index), fn x -> x |> unquote(tail) end)
end
end
end
defp create_updater({:tuple, index}, tail, acc_items) when is_var(index) do
quote do
tuple
when is_tuple(tuple) and is_integer(unquote(index)) and
unquote(index) >= 0 and
tuple_size(tuple) <= unquote(index) ->
len = max(unquote(index) - tuple_size(tuple), 0)
List.to_tuple(Tuple.to_list(tuple) ++ List.duplicate(nil, len) ++ [unquote(acc_items)])
tuple
when is_tuple(tuple) and is_integer(unquote(index)) and
unquote(index) < 0 and
tuple_size(tuple) < -unquote(index) ->
len = max(-unquote(index) - tuple_size(tuple) - 1, 0)
List.to_tuple([unquote(acc_items) | List.duplicate(nil, len)] ++ Tuple.to_list(tuple))
tuple
when is_tuple(tuple) and is_integer(unquote(index)) and
unquote(index) < 0 ->
index = tuple_size(tuple) + unquote(index) + 1
val = :erlang.element(index, tuple) |> unquote(tail)
:erlang.setelement(index, tuple, val)
tuple
when is_tuple(tuple) and is_integer(unquote(index)) and
unquote(index) >= 0 ->
indexplusone = unquote(index) + 1
val =
indexplusone
|> :erlang.element(tuple)
|> unquote(tail)
:erlang.setelement(indexplusone, tuple, val)
end
end
defp create_updater(path_item, tail, _) do
create_updater(path_item, tail)
end
defp add_to_acc({:map, item}, acc_items) do
quote(do: %{unquote(item) => unquote(acc_items)})
end
defp add_to_acc({:keyword, item}, acc_items) do
quote(do: [{unquote(item), unquote(acc_items)}])
end
defp add_to_acc({type, 0}, acc_items) do
collection(type, [acc_items])
end
defp add_to_acc({type, -1}, acc_items) do
collection(type, [acc_items])
end
defp add_to_acc({type, n}, acc_items) when is_integer(n) and n > 0 do
nils = for _ <- 1..n, do: nil
collection(type, nils ++ [acc_items])
end
defp add_to_acc({type, n}, acc_items) when is_integer(n) and n < 0 do
nils = for _ <- 1..(-n), do: nil
collection(type, [acc_items | nils])
end
defp add_to_acc({:list, var}, acc_items) do
quote do
case unquote(var) do
0 ->
[unquote(acc_items)]
n when is_integer(n) ->
for(_ <- 1..abs(n), do: nil) ++ [unquote(acc_items)]
end
end
end
defp add_to_acc({:tuple, var}, acc_items) do
quote do
case unquote(var) do
0 ->
{unquote(acc_items)}
n when is_integer(n) ->
List.to_tuple(List.duplicate(nil, n) ++ [unquote(acc_items)])
end
end
end
defp fallback_from_acc(path_items, acc) do
Enum.flat_map(path_items, &gen_fallback(&1, acc))
end
defp gen_fallback({:map, key}, acc) do
quote do
%{} = other -> Map.put(other, unquote(key), unquote(acc))
end
end
defp gen_fallback({:list, -1}, acc) do
quote do
l when is_list(l) ->
[unquote(acc) | l]
end
end
defp gen_fallback({:list, n}, acc) when is_integer(n) and n < 0 do
quote do
l when is_list(l) ->
len = max(unquote(-n - 1) - length(l), 0)
[unquote(acc) | List.duplicate(nil, len)] ++ l
end
end
defp gen_fallback({:list, n}, acc) when is_integer(n) do
quote do
l when is_list(l) ->
len = max(unquote(n) - length(l), 0)
l ++ List.duplicate(nil, len) ++ [unquote(acc)]
end
end
defp gen_fallback({:list, n}, acc) do
quote do
l when is_list(l) and unquote(n) == -1 ->
[unquote(acc) | l]
l when is_list(l) and is_integer(unquote(n)) and unquote(n) >= 0 ->
len = max(unquote(n) - length(l), 0)
l ++ List.duplicate(nil, len) ++ [unquote(acc)]
l when is_list(l) and is_integer(unquote(n)) ->
len = max(-unquote(n) - 1 - length(l), 0)
[unquote(acc) | List.duplicate(nil, len)] ++ l
end
end
defp gen_fallback({:tuple, n}, acc) when is_integer(n) and n >= 0 do
quote do
t when is_tuple(t) ->
len = unquote(n) - tuple_size(t)
List.to_tuple(Tuple.to_list(t) ++ List.duplicate(nil, len) ++ [unquote(acc)])
end
end
defp gen_fallback({:tuple, n}, acc) when is_integer(n) and n < 0 do
quote do
t when is_tuple(t) ->
len = -unquote(n) - tuple_size(t) - 1
List.to_tuple([unquote(acc) | List.duplicate(nil, len)] ++ Tuple.to_list(t))
end
end
defp gen_fallback({:tuple, n}, acc) do
quote do
t when is_tuple(t) and is_integer(unquote(n)) and unquote(n) >= 0 ->
len = unquote(n) - tuple_size(t)
List.to_tuple(Tuple.to_list(t) ++ List.duplicate(nil, len) ++ [unquote(acc)])
t when is_tuple(t) and is_integer(unquote(n)) ->
len = -unquote(n) - tuple_size(t) - 1
List.to_tuple([unquote(acc) | List.duplicate(nil, len)] ++ Tuple.to_list(t))
end
end
defp gen_fallback({:keyword, key}, acc) do
quote do
kwd when is_list(kwd) ->
[{unquote(key), unquote(acc)} | kwd]
end
end
defp absolute_fallback do
quote do
_ -> throw(:path_not_found)
end
end
defp collection(:list, items) do
quote do: [unquote_splicing(items)]
end
defp collection(:tuple, items) do
quote do: {unquote_splicing(items)}
end
end