defmodule LogicalFile.Macro do
  alias LogicalFile

  @moduledoc """
  A `Macro` represents a `LogicalFile` transformation.

  Each `Macro` is implemented by a module through the `apply_macro` callback
  with zero or more arguments in a keyword list.

  A '`Macro` invocation' is specified as a tuple in the form
  `{module, [options keyword list]}`. A macro is called through the
  `apply_macro` callback which takes a `LogicalFile` and returns a
  possibly transformed `LogicalFile`.

  The macro processor applies macros in turn to a base `LogicalFile` and each
  macro is expected to return a valid `LogicalFile`.

  Sample macro implementations are provided for handling file includes
  (`LogicalFile.Macros.Include`) and single-line comments

  @doc "generate macro invocation"
  @callback invocation(options :: list()) :: tuple()

  @doc "perform macro behaviour on a LogicalFile"
  @callback apply_macro(file :: LogicalFile.t(), options :: list()) :: LogicalFile.t()

  @doc """
  `apply_macros/2` takes a `LogicalFile` and a list of macro invocations
  and applies each macro to transform the `LogicalFile`.
  def apply_macros(%LogicalFile{} = unprocessed_file, macro_list) do
    Enum.reduce(macro_list, unprocessed_file, fn {module, options}, file ->
      apply(module, :apply_macro, [file, options])