lib/ethyl.ex

defmodule Ethyl do
  @moduledoc """
  A non-general, purely functional subset of Elixir
  """

  alias Ethyl.{AstTransforms, Context}

  @doc """
  Compiles an ethyl reduction

  This can be used to write ethyl code inline in Elixir code
  """
  defmacro ethyl(do: body) do
    Ethyl.from_elixir_ast(
      body,
      Context.from_elixir_env(__CALLER__)
    )
  end

  @doc """
  Transpiles an Elixir AST into an Ethyl AST
  """
  @spec from_elixir_ast(ast :: tuple(), context :: Context.t()) ::
          ast :: tuple()
  def from_elixir_ast(ast, context) do
    ast
    |> AstTransforms.expand_aliases()
    |> AstTransforms.alter_compile_directives(context)
    |> AstTransforms.expand_imports()
    |> AstTransforms.expand_apply_3()
    |> AstTransforms.recursive_expand()
  end

  @doc """
  Evaluates an ethyl expression in the given path with the given context

  This function is used under the hood by `from_elixir_ast/2` when using
  `import`.

  Note that this function calls out to `Code.eval_quoted/3` which is
  potentially dangerous: only use this function if you know ahead of
  time that the contents of `path` are safe to evaluate.
  """
  @spec eval_file!(String.t(), Context.t(), Code.binding(), Keyword.t()) ::
          {term(), Code.binding()}
  def eval_file!(path, context, bindings \\ [], opts \\ []) do
    opts = Keyword.merge([file: path], opts)

    path
    |> File.read!()
    |> Code.string_to_quoted!()
    |> from_elixir_ast(context)
    |> Code.eval_quoted(bindings, opts)
  end
end