lib/kinda_codegen.ex

defmodule Kinda.CodeGen do
  @moduledoc """
  Behavior for customizing your source code generation.
  """

  alias Kinda.CodeGen.{KindDecl, NIFDecl}

  defmacro __using__(opts) do
    quote do
      mod = Keyword.fetch!(unquote(opts), :with)
      root = Keyword.fetch!(unquote(opts), :root)
      forward = Keyword.fetch!(unquote(opts), :forward)

      Kinda.CodeGen.nif_ast(mod.kinds(), mod.nifs(), root, forward)
      |> then(&Module.eval_quoted(__MODULE__, &1))
    end
  end

  @callback kinds() :: KindDecl.t()
  @callback nifs() :: [{atom(), integer()}]
  def kinds(), do: []

  def nif_ast(kinds, nifs, root_module, forward_module) do
    # generate stubs for generated NIFs
    extra_kind_nifs =
      kinds
      |> Enum.map(&NIFDecl.from_resource_kind/1)
      |> List.flatten()

    for nif <- nifs ++ extra_kind_nifs do
      nif =
        case nif do
          {wrapper_name, arity} when is_atom(wrapper_name) and is_integer(arity) ->
            %NIFDecl{
              wrapper_name: wrapper_name,
              nif_name: Module.concat(root_module, wrapper_name),
              arity: arity
            }

          %NIFDecl{} ->
            nif
        end

      args_ast = Macro.generate_unique_arguments(nif.arity, __MODULE__)

      %NIFDecl{wrapper_name: wrapper_name, nif_name: nif_name} = nif

      wrapper_name =
        if is_bitstring(wrapper_name) do
          String.to_atom(wrapper_name)
        else
          wrapper_name
        end

      quote do
        @doc false
        def unquote(nif_name)(unquote_splicing(args_ast)),
          do:
            raise(
              "NIF for resource kind is not implemented, or failed to load NIF library. Function: :\"#{unquote(nif_name)}\"/#{unquote(nif.arity)}"
            )

        def unquote(wrapper_name)(unquote_splicing(args_ast)) do
          refs = Kinda.unwrap_ref([unquote_splicing(args_ast)])
          ret = apply(__MODULE__, unquote(nif_name), refs)
          unquote(forward_module).check!(ret)
        end
      end
    end
    |> List.flatten()
  end
end