lib/hologram/compiler/transformer.ex

defmodule Hologram.Compiler.Transformer do
  alias Hologram.Compiler.{Context, Reflection}

  alias Hologram.Compiler.{
    AccessOperatorTransformer,
    AdditionOperatorTransformer,
    AliasDirectiveTransformer,
    AnonymousFunctionTypeTransformer,
    BinaryTypeTransformer,
    BooleanAndOperatorTransformer,
    DotOperatorTransformer,
    EqualToOperatorTransformer,
    FunctionDefinitionTransformer,
    FunctionCallTransformer,
    IfExpressionTransformer,
    ImportDirectiveTransformer,
    ListTypeTransformer,
    MacroDefinitionTransformer,
    MapTypeTransformer,
    MatchOperatorTransformer,
    ModuleAttributeDefinitionTransformer,
    ModuleDefinitionTransformer,
    ModuleTypeTransformer,
    QuoteTransformer,
    PipeOperatorTransformer,
    RequireDirectiveTransformer,
    StructTypeTransformer,
    TypeOperatorTransformer,
    TupleTypeTransformer,
    UnquoteTransformer,
    UseDirectiveTransformer
  }

  alias Hologram.Compiler.IR.{
    AtomType,
    BooleanType,
    IntegerType,
    ModuleAttributeOperator,
    ModulePseudoVariable,
    NilType,
    ProtocolDefinition,
    StringType,
    Typespec,
    Variable
  }

  # TYPES

  def transform({:fn, _, _} = ast, %Context{} = context) do
    AnonymousFunctionTypeTransformer.transform(ast, context)
  end

  def transform(ast, %Context{} = context) when is_atom(ast) and ast not in [nil, false, true] do
    if Reflection.module?(ast) do
      ModuleTypeTransformer.transform(ast, context)
    else
      %AtomType{value: ast}
    end
  end

  def transform({:<<>>, _, _} = ast, %Context{} = context) do
    BinaryTypeTransformer.transform(ast, context)
  end

  def transform(ast, _) when is_boolean(ast) do
    %BooleanType{value: ast}
  end

  def transform(ast, _) when is_integer(ast) do
    %IntegerType{value: ast}
  end

  def transform(ast, %Context{} = context) when is_list(ast) do
    ListTypeTransformer.transform(ast, context)
  end

  def transform({:%{}, _, _} = ast, %Context{} = context) do
    MapTypeTransformer.transform(ast, context)
  end

  def transform({:__aliases__, _, _} = ast, %Context{} = context) do
    ModuleTypeTransformer.transform(ast, context)
  end

  def transform(nil, _) do
    %NilType{}
  end

  def transform(ast, _) when is_binary(ast) do
    %StringType{value: ast}
  end

  def transform({:%, _, _} = ast, %Context{} = context) do
    StructTypeTransformer.transform(ast, context)
  end

  def transform({:{}, _, _} = ast, %Context{} = context) do
    TupleTypeTransformer.transform(ast, context)
  end

  def transform({_, _} = ast, %Context{} = context) do
    TupleTypeTransformer.transform(ast, context)
  end

  # OPERATORS

  def transform({{:., _, [Access, :get]}, _, _} = ast, %Context{} = context) do
    AccessOperatorTransformer.transform(ast, context)
  end

  def transform({:+, _, _} = ast, %Context{} = context) do
    AdditionOperatorTransformer.transform(ast, context)
  end

  def transform({:&&, _, _} = ast, %Context{} = context) do
    BooleanAndOperatorTransformer.transform(ast, context)
  end

  def transform({{:., _, _}, [no_parens: true, line: _], _} = ast, %Context{} = context) do
    DotOperatorTransformer.transform(ast, context)
  end

  def transform({:==, _, _} = ast, %Context{} = context) do
    EqualToOperatorTransformer.transform(ast, context)
  end

  def transform({:=, _, _} = ast, %Context{} = context) do
    MatchOperatorTransformer.transform(ast, context)
  end

  def transform({:@, _, [{:spec, _, [{:"::", _, _}]}]}, _) do
    %Typespec{}
  end

  def transform({:@, _, [{name, _, ast}]}, _) when not is_list(ast) do
    %ModuleAttributeOperator{name: name}
  end

  def transform({:|>, _, _} = ast, %Context{} = context) do
    PipeOperatorTransformer.transform(ast, context)
  end

  def transform({:"::", _, _} = ast, %Context{} = context) do
    TypeOperatorTransformer.transform(ast, context)
  end

  # DEFINITIONS

  def transform({:def, _, _} = ast, %Context{} = context) do
    FunctionDefinitionTransformer.transform(ast, context)
  end

  def transform({:defp, _, _} = ast, %Context{} = context) do
    FunctionDefinitionTransformer.transform(ast, context)
  end

  def transform({:defmacro, _, _} = ast, %Context{} = context) do
    MacroDefinitionTransformer.transform(ast, context)
  end

  def transform({:defmodule, _, _} = ast, _) do
    ModuleDefinitionTransformer.transform(ast)
  end

  # DEFER: implement
  def transform({:defprotocol, _, _}, _) do
    %ProtocolDefinition{}
  end

  def transform({:@, _, [{_, _, exprs}]} = ast, %Context{} = context) when is_list(exprs) do
    ModuleAttributeDefinitionTransformer.transform(ast, context)
  end

  # DIRECTIVES

  def transform({:alias, _, _} = ast, _) do
    AliasDirectiveTransformer.transform(ast)
  end

  def transform({:import, _, _} = ast, _) do
    ImportDirectiveTransformer.transform(ast)
  end

  def transform({:require, _, _} = ast, _) do
    RequireDirectiveTransformer.transform(ast)
  end

  def transform({:use, _, _} = ast, _) do
    UseDirectiveTransformer.transform(ast)
  end

  # CONTROL FLOW

  def transform({:if, _, _} = ast, %Context{} = context) do
    IfExpressionTransformer.transform(ast, context)
  end

  # OTHER

  def transform({{:., _, _}, _, _} = ast, %Context{} = context) do
    FunctionCallTransformer.transform(ast, context)
  end

  def transform({:quote, _, _} = ast, %Context{} = context) do
    QuoteTransformer.transform(ast, context)
  end

  def transform({:unquote, _, _} = ast, %Context{} = context) do
    UnquoteTransformer.transform(ast, context)
  end

  # this needs to be defined before variable case
  def transform({:__MODULE__, _, _}, _) do
    %ModulePseudoVariable{}
  end

  def transform({name, _, nil}, _) when is_atom(name) do
    %Variable{name: name}
  end

  def transform({name, _, module}, _) when is_atom(name) and is_atom(module) do
    %Variable{name: name}
  end

  # this needs to be defined after variable case
  def transform({function, _, _} = ast, %Context{} = context) when is_atom(function) do
    FunctionCallTransformer.transform(ast, context)
  end
end