lib/qh/expr.ex

defmodule Qh.Expr do
  def maybe_deep_prefix_binding(skip_cond, tree) do
    if skip_cond do
      tree
    else
      deep_prefix_binding(tree)
    end
  end

  def deep_prefix_binding(tree, prefix \\ {:t, [], nil}) do
    conditional_prewalk(tree, fn
      {:^, _, _} = node ->
        {false, node}

      {field, _, nil} ->
        {false, {{:., [], [prefix, field]}, [no_parens: true], []}}

      node ->
        {true, node}
    end)
  end

  def conditional_prewalk(tree, fun) do
    apply_and_maybe_descend(tree, fun)
  end

  defp do_prewalk_cond({form, meta, args}, fun) when is_atom(form) do
    {form, meta, do_prewalk_cond_args(args, fun)}
  end

  defp do_prewalk_cond({form, meta, args}, fun) do
    {apply_and_maybe_descend(form, fun), meta, do_prewalk_cond_args(args, fun)}
  end

  defp do_prewalk_cond({left, right}, fun) do
    {apply_and_maybe_descend(left, fun), apply_and_maybe_descend(right, fun)}
  end

  defp do_prewalk_cond(list, fun) when is_list(list) do
    do_prewalk_cond_args(list, fun)
  end

  defp do_prewalk_cond(x, _fun) do
    x
  end

  defp do_prewalk_cond_args(args, _fun) when is_atom(args) do
    args
  end

  defp do_prewalk_cond_args(args, fun) when is_list(args) do
    Enum.map(args, fn node -> apply_and_maybe_descend(node, fun) end)
  end

  defp apply_and_maybe_descend(node, fun) do
    case fun.(node) do
      {true, node} ->
        do_prewalk_cond(node, fun)

      {false, node} ->
        node
    end
  end
end