defmodule Zig.Type.Struct do
alias Zig.Type
use Type
defstruct [:name, :packed, :required, :optional, extern: false, mutable: false]
@type t :: %{
name: String.t(),
packed: nil | non_neg_integer(),
extern: nil | non_neg_integer(),
required: %{optional(atom) => Type.t()},
optional: %{optional(atom) => Type.t()},
mutable: boolean
}
def from_json(%{"name" => name, "fields" => fields} = json, module) do
{required, optional} = Enum.split_with(fields, & &1["required"])
to_field = fn desc -> {String.to_atom(desc["name"]), Type.from_json(desc["type"], module)} end
%__MODULE__{
name: String.trim_leading(name, ".#{module}."),
packed: Map.get(json, "packed_size"),
extern: Map.get(json, "extern", false),
required: Map.new(required, to_field),
optional: Map.new(optional, to_field)
}
end
def marshal_param(%{packed: packed}, _) when rem(packed, 8) != 0 do
# padding bits
padding = 8 - rem(packed, 8)
fn arg, _index ->
quote do
if is_bitstring(unquote(arg)) do
<<0::unquote(padding), unquote(arg)::bitstring>>
|> :erlang.binary_to_list()
|> Enum.reverse()
|> :erlang.list_to_binary()
else
unquote(arg)
end
end
end
end
def marshal_param(_, _), do: nil
def to_string(struct), do: "#{mut(struct)}#{struct.name}"
def to_call(struct), do: "#{mut(struct)}nif.#{struct.name}"
def spec(struct, :param, opts) do
optional = to_fields(struct.optional, :optional, :param, opts)
keyword = to_fields(struct.optional, :untagged, :param, opts)
required = to_fields(struct.required, :untagged, :param, opts)
if binary_form = binary_form(struct) do
quote do
unquote(map_spec(optional, required))
| unquote(keyword ++ required)
| unquote(binary_form)
end
else
quote do
unquote(map_spec(optional, required)) | unquote(keyword ++ required)
end
end
end
def spec(struct, :return, opts) do
binary_form = binary_form(struct)
case Keyword.fetch!(opts, :type) do
:binary when not is_nil(binary_form) ->
binary_form
t when t in ~w(charlist binary default)a ->
all_fields =
struct.optional
|> Map.merge(struct.required)
|> to_fields(:required, :return, opts)
map_spec([], all_fields)
end
end
defp map_spec(optional, required) do
quote context: Elixir do
%{unquote_splicing(optional ++ required)}
end
end
defp binary_form(%{packed: int}) when is_integer(int) do
quote context: Elixir do
<<_::unquote(int * 8)>>
end
end
defp binary_form(_struct), do: nil
defp to_fields(portion, mode, context, opts) do
portion
|> Enum.map(fn
{k, v} when mode == :optional ->
{quote do
optional(unquote(k))
end, Type.spec(v, context, opts)}
{k, v} ->
{k, Type.spec(v, context, opts)}
end)
|> Enum.sort()
end
defp mut(struct), do: if(struct.mutable, do: "*")
def return_allowed?(struct) do
struct.required
|> Map.values()
|> Kernel.++(Map.values(struct.optional))
|> Enum.map(&Type.return_allowed?/1)
|> Enum.all?()
end
end