lib/hype_lib/d_s_l/parser.ex

defmodule HypeLib.DSL.Parser do
  @moduledoc """
  ## Changelog

  ### 2.3.1

  Fixed credo errors
  """

  use HypeLib.Prelude

  # Type definitions

  @type! t() :: module()

  @type! state() :: term()

  @type! ast_element() :: term()

  @type! parse_result() :: {ast_element(), state()}

  # Callbacks

  @callback on_init() :: state()

  @callback on_node(element :: ast_element(), parser_state :: state()) :: parse_result()

  @callback on_finish(element :: ast_element(), parser_state :: state()) :: parse_result()

  @optional_callbacks on_init: 0, on_finish: 2

  # region `use` macro

  defmacro __using__(_args) do
    quote do
      alias HypeLib.DSL.Parser

      @behaviour Parser
    end
  end

  # Helper functions

  @doc """
  Returns the initial state of the parser
  """
  def get_initial_state(parser_module), do: parser_module.on_init()

  @spec! is_parser(module_to_check :: term()) :: boolean
  def is_parser(module_to_check) do
    check_parser_module(module_to_check)

    true
  rescue
    _ ->
      false
  end

  @spec! check_parser_module(module_to_check :: term()) :: nil | none()
  def check_parser_module(module_to_check) do
    unless is_atom(module_to_check) do
      raise __MODULE__.Error.NoModuleError, module_to_check
    end

    unless is_module(module_to_check) do
      raise __MODULE__.Error.NoModuleError, module_to_check
    end

    unless has_on_node(module_to_check) do
      raise __MODULE__.Error.MissingLifecycleMethodError, {module_to_check, :on_node, 2}
    end
  end

  def unhandled_element(element, parser_state) do
    IO.puts("-------- UNHANDLED AST ELEMENT --------")
    IO.puts("Found undhandled element:")
    # credo:disable-for-next-line
    IO.inspect(element)
    IO.puts("")
    IO.puts("At state:")
    # credo:disable-for-next-line
    IO.inspect(parser_state)
    IO.puts("---------------------------------------")

    {element, parser_state}
  end

  defp is_module(module_to_check), do: function_exported?(module_to_check, :__info__, 1)

  defp has_on_node(module_to_check), do: function_exported?(module_to_check, :on_node, 2)
end