defmodule Aja.Vector.Builder do @moduledoc false alias Aja.Vector.{Tail, Node} require Aja.Vector.CodeGen, as: C def from_list(list) when is_list(list) do do_concat_list([], list) end def concat_list(builder, list) when is_list(builder) and is_list(list) do do_concat_list(builder, list) end defp do_concat_list(builder, unquote(C.list_with_rest(C.var(rest)))) when rest != [] do leaf = unquote(C.array()) builder |> append_node(leaf) |> do_concat_list(rest) end defp do_concat_list(builder, rest) do {builder, length(rest), Tail.partial_from_list(rest)} end def map_from_list(list, fun) when is_list(list) and is_function(fun, 1) do concat_map_list([], list, fun) end defp concat_map_list(builder, unquote(C.list_with_rest(C.var(rest))), fun) when rest != [] do leaf = unquote(C.arguments() |> Enum.map(C.apply_mapper(C.var(fun))) |> C.array()) builder |> append_node(leaf) |> concat_map_list(rest, fun) end defp concat_map_list(builder, rest, fun) do {builder, length(rest), Enum.map(rest, fun) |> Tail.partial_from_list()} end def append_nodes(builder, []), do: builder def append_nodes(builder, [node | nodes]) do builder |> append_node(node) |> append_nodes(nodes) end def append_nodes_with_offset(builder, [node], offset, tail, tail_size) do shifted_tail = Node.shift(tail, -tail_size) last_node = Node.from_offset_nodes(node, shifted_tail, offset) case tail_size - offset do new_tail_size when new_tail_size > 0 -> new_tail = shifted_tail |> Node.from_offset_nodes(unquote(C.fill_with([], nil) |> C.array()), offset) |> Node.shift(new_tail_size) builder |> append_node(last_node) |> append_tail(new_tail, new_tail_size) tail_size_complement -> new_tail = Node.shift(last_node, tail_size_complement) append_tail(builder, new_tail, C.branch_factor() + tail_size_complement) end end def append_nodes_with_offset(builder, [node1 | nodes = [node2 | _]], offset, tail, tail_size) do node = Node.from_offset_nodes(node1, node2, offset) builder |> append_node(node) |> append_nodes_with_offset(nodes, offset, tail, tail_size) end def append_node(builder, node) def append_node([], node), do: [[node]] def append_node([unquote(C.arguments() |> tl()) | tries], unquote(C.arguments() |> hd())) do trie_node = unquote(C.reversed_arguments() |> C.array()) [[] | append_node(tries, trie_node)] end def append_node([trie | tries], node) do [[node | trie] | tries] end def append_tail(builder, tail, tail_size) do {builder, tail_size, tail} end def to_trie([[] | tries], level) do to_trie(tries, C.incr_level(level)) end def to_trie([[dense_trie]], level) do {level, dense_trie} end def to_trie(sparse_trie, level) do to_sparse_trie(sparse_trie, level) end defp to_sparse_trie([children | rest], level) do node = Node.from_incomplete_reverse_list(children) case rest do [] -> {C.incr_level(level), node} [head | tail] -> to_sparse_trie([[node | head] | tail], C.incr_level(level)) end end def from_trie(trie, level, amount) do do_from_trie(trie, level, amount, []) end def do_from_trie(_trie, _level, 0, acc), do: [[] | acc] def do_from_trie(trie, level, amount, acc) do case :erlang.bsr(amount, level) do C.branch_factor() -> prepend_single_builder([[trie] | acc], level) full_nodes_count -> reverse_full_nodes = Node.take_reverse(trie, full_nodes_count) acc = [reverse_full_nodes | acc] case amount - :erlang.bsl(full_nodes_count, level) do 0 -> prepend_single_builder(acc, C.decr_level(level)) remaining -> :erlang.element(full_nodes_count + 1, trie) |> do_from_trie(C.decr_level(level), remaining, acc) end end end defp prepend_single_builder(list, _level = 0), do: list defp prepend_single_builder(list, level) do prepend_single_builder([[] | list], C.decr_level(level)) end @compile {:inline, tail_offset: 3} def tail_offset([], _level, acc), do: acc def tail_offset([trie | tries], level, acc) do trie_size = length(trie) |> Bitwise.bsl(level) tail_offset(tries, C.incr_level(level), acc + trie_size) end end