lib/live_data/tracked/flat_ast/util/transcribe.ex

defmodule LiveData.Tracked.FlatAst.Util.Transcribe do
  alias LiveData.Tracked.FlatAst
  alias LiveData.Tracked.FlatAst.Expr
  alias LiveData.Tracked.FlatAst.PDAst
  alias LiveData.Tracked.FlatAst.Util

  @doc """
  Directly transcribes the given selection from the input AST to the output.

  When a value is not found in the regular `map`, `backup_resolve/1` is called.
  This is useful in the case where we have a separate, semantically different
  map of parent transcribed expressions.
  """
  def transcribe({:literal, _} = lit_id, data, map, _backup_resolve, out) do
    {:literal, lit} = FlatAst.get(data.ast, lit_id)
    new_lit_id = PDAst.add_literal(out, lit)
    {new_lit_id, map}
  end

  def transcribe(expr_id, data, map, backup_resolve, out) do
    false = Map.has_key?(map, expr_id)
    expr = FlatAst.get(data.ast, expr_id)

    {new_expr_id, map} = transcribe(expr, expr_id, data, map, backup_resolve, out)
    map = Map.put(map, expr_id, new_expr_id)

    {new_expr_id, map}
  end

  def transcribe(%Expr.Scope{exprs: exprs}, _expr_id, data, map, backup_resolve, out) do
    {new_exprs, _map} =
      Enum.map_reduce(exprs, map, fn expr, map ->
        transcribe(expr, data, map, backup_resolve, out)
      end)

    new_expr = %Expr.Scope{exprs: new_exprs}
    new_expr_id = PDAst.add_expr(out, new_expr)

    {new_expr_id, map}
  end

  #def transcribe(%Expr.Case{} = case_expr, _expr_id, data, map, backup_resolve, out) do
  #  true = false
  #end

  def transcribe(expr, expr_id, data, map, backup_resolve, out) do
    new_expr_id = PDAst.add_expr(out)
    map = Map.put(map, expr_id, new_expr_id)

    {new_expr, map} =
      Util.transform_expr(expr, map, fn
        :value, _selector, inner_expr_id, map ->
          new_expr_id = transcribe_maybe_scope(inner_expr_id, data, map, backup_resolve, out)
          {new_expr_id, map}

        :scope, _selector, inner_expr_id, map ->
          new_expr_id = transcribe_maybe_scope(inner_expr_id, data, map, backup_resolve, out)
          {new_expr_id, map}

        :pattern, _selector, {pattern, binds}, map ->
          {{pattern, binds}, map}

        :literal, _selector, literal, map ->
          {:literal, lit} = FlatAst.get(data.ast, literal)
          new_lit_id = PDAst.add_literal(out, lit)
          {new_lit_id, map}

        :ref, _selector, {:expr_bind, eid, bind_selector}, map ->
          {:expr, new_eid} = Map.get(map, {:expr, eid}) || backup_resolve.({:expr, eid})
          new_ref = {:expr_bind, new_eid, bind_selector}

          {new_ref, map}

        :ref, _selector, ref_expr, map ->
          {Map.fetch!(map, ref_expr), map}
      end)

    :ok = PDAst.set_expr(out, new_expr_id, new_expr)

    {new_expr_id, map}
  end

  def transcribe_maybe_scope(expr_id, data, map, backup_resolve, out) do
    case FlatAst.get(data.ast, expr_id) do
      %Expr.Scope{} ->
        {new_expr_id, _map} = transcribe(expr_id, data, map, backup_resolve, out)
        new_expr_id

      {:expr_bind, _eid, _selector} = bind ->
        transcribe_bind(bind, map, backup_resolve)

      {:literal, lit} ->
        PDAst.add_literal(out, lit)

      _ ->
        Map.fetch!(map, expr_id)
    end
  end

  def transcribe_bind({:expr_bind, eid, selector}, map, backup_resolve) do
    expr_id = {:expr, eid}
    {:expr, new_eid} = Map.get(map, expr_id) || backup_resolve.(expr_id)
    {:expr_bind, new_eid, selector}
  end
end