lib/zig/type/cpointer.ex

defmodule Zig.Type.Cpointer do
  alias Zig.Type
  alias Zig.Type.Struct
  use Type

  import Type, only: :macros

  defstruct [:child]

  @type t :: %__MODULE__{child: Type.t()}

  def from_json(%{"child" => child}, module) do
    %__MODULE__{child: Type.from_json(child, module)}
  end

  def to_string(slice), do: "[*c]#{Kernel.to_string(slice.child)}"

  def to_call(slice), do: "[*c]#{Type.to_call(slice.child)}"

  def return_allowed?(pointer) do
    case pointer.child do
      ~t(u8) -> true
      # null-terminated list of pointers.
      %__MODULE__{child: child} -> Type.return_allowed?(child)
      # NB: we assume these are single pointer returns.
      struct = %Struct{} -> Type.return_allowed?(struct)
      _ -> false
    end
  end

  def spec(%{child: child}, :param, _opts) do
    has_solo? = match?(%Type.Struct{extern: true}, child)
    child_form = Type.spec(child, :param, [])

    case {has_solo?, binary_form(child)} do
      {false, nil} ->
        quote do
          [unquote(child_form)] | nil
        end

      {true, nil} ->
        quote do
          unquote(child_form) | [unquote(child_form)] | nil
        end

      {false, binary_form} ->
        quote do
          [unquote(child_form)] | unquote(binary_form) | nil
        end
    end
  end

  def spec(%{child: ~t(u8)}, :return, opts) do
    # assumed to be a null-terminated string
    case {opts[:length], Keyword.fetch!(opts, :type)} do
      {_, :list} ->
        quote context: Elixir do
          [0..255]
        end

      {length, type} when not is_nil(length) and type in ~w(default binary)a ->
        quote context: Elixir do
          <<_::unquote(length * 8)>>
        end

      {_, type} when type in ~w(default binary)a ->
        Type.spec(:binary)
    end
  end

  def spec(%{child: child}, :return, opts) do
    case {Keyword.fetch(opts, :length), Keyword.fetch!(opts, :type)} do
      {{:ok, _}, :default} ->
        [Type.spec(child, :return, opts)]

      {{:ok, {:arg, _}}, :binary} ->
        # this is the case where the length is drawn from one of the arguments
        # to the function.
        quote do
          <<_::_*unquote(chunk_size(child))>>
        end

      {{:ok, number}, :binary} ->
        quote do
          <<_::unquote(number * chunk_size(child))>>
        end

      {:error, _} when child.__struct__ == Type.Struct ->
        Type.spec(child, :return, opts)

      {:error, _} when child == %__MODULE__{child: ~t(u8)} ->
        quote do
          [binary()]
        end

      {:error, _} when child.__struct__ == __MODULE__ ->
        [Type.spec(child.child, :return, opts)]

      {:error, _} ->
        raise "missing length not allowed"
    end
  end

  def spec(%{child: child = %__MODULE__{}}, :return, opts) do
    [Type.spec(child, :return, opts)]
  end

  defp chunk_size(%type{bits: bits}) when type in [Type.Integer, Type.Float] do
    bits
  end

  defp chunk_size(_), do: raise("invalid type for binary *c output")

  defp binary_form(~t(u8)), do: Type.spec(:binary)

  defp binary_form(%Type.Integer{bits: bits}) do
    quote context: Elixir do
      <<_::_*unquote(Type.Integer._next_power_of_two_ceil(bits))>>
    end
  end

  defp binary_form(%Type.Float{bits: bits}) do
    quote context: Elixir do
      <<_::_*unquote(bits)>>
    end
  end

  defp binary_form(_), do: nil

  def missing_size?(_), do: true

  def of(child), do: %__MODULE__{child: child}
end