Current section

Files

Jump to
zigler lib zig type struct.ex
Raw

lib/zig/type/struct.ex

defmodule Zig.Type.Struct do
@moduledoc false
alias Zig.Parameter
alias Zig.Return
alias Zig.Type
use Type
defstruct [:name, :packed, :required, :optional, :extern, 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_size"),
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
@impl true
def render_elixir_spec(struct, %Return{as: as}) do
render_elixir_spec(struct, as)
end
def render_elixir_spec(struct, %Parameter{} = params) do
optional = to_fields(struct.optional, :optional, params)
keyword = to_fields(struct.optional, :untagged, params)
required = to_fields(struct.required, :untagged, params)
map_typespec = map_spec(optional, required)
keyword_typespec = keyword ++ required
if binary_typespec = Type.binary_typespec(struct) do
quote context: Elixir do
unquote(map_typespec) | unquote(keyword_typespec) | unquote(binary_typespec)
end
else
quote do
unquote(map_typespec) | unquote(keyword_typespec)
end
end
end
def render_elixir_spec(struct, :binary), do: Type.binary_typespec(struct)
def render_elixir_spec(%{packed: packed} = struct, :default) when is_integer(packed) do
Type.binary_typespec(struct)
end
# default map form. Handles `:default`, `:map`, and `{:map, ...}` specs
def render_elixir_spec(struct, context) do
all_fields =
struct.optional
|> Map.merge(struct.required)
|> to_fields(:required, context)
map_spec([], all_fields)
end
@impl true
def render_erlang_spec(struct, %Return{as: as}), do: render_erlang_spec(struct, as)
def render_erlang_spec(struct, %Parameter{} = params) do
optional = to_erlang_fields(struct.optional, params)
required = to_erlang_fields(struct.required, params)
"\#{#{Enum.join(optional ++ required, ", ")}}"
end
def render_erlang_spec(_struct, :binary), do: "binary()"
def render_erlang_spec(%{packed: packed}, :default) when is_integer(packed), do: "binary()"
# default map form
def render_erlang_spec(struct, context) do
all_fields =
struct.optional
|> Map.merge(struct.required)
|> to_erlang_fields(context)
"\#{#{Enum.join(all_fields, ", ")}}"
end
defp to_erlang_fields(portion, opts) do
portion
|> Enum.map(fn {k, v} ->
key_opts = key_opts(opts, k)
"#{k} := #{Type.render_erlang_spec(v, key_opts)}"
end)
|> Enum.sort()
end
defp map_spec(optional, required) do
quote context: Elixir do
%{unquote_splicing(optional ++ required)}
end
end
defp to_fields(portion, mode, opts) do
portion
|> Enum.map(fn
{k, v} when mode == :optional ->
key_opts = key_opts(opts, k)
{quote do
optional(unquote(k))
end, Type.render_elixir_spec(v, key_opts)}
{k, v} ->
key_opts = key_opts(opts, k)
{k, Type.render_elixir_spec(v, key_opts)}
end)
|> Enum.sort()
end
defp key_opts({:map, keyword}, key) do
Keyword.get(keyword, key, :default)
end
defp key_opts(%Parameter{} = parameter, _), do: parameter
defp key_opts(_, _), do: :default
@impl true
def render_zig(%{name: name}), do: "nif.#{name}"
# for now. Later, we will need to do more sophisticated checks
@impl true
def get_allowed?(struct) do
all_values(struct, &Type.get_allowed?/1)
end
@impl true
def make_allowed?(struct) do
all_values(struct, &Type.make_allowed?/1)
end
@impl true
def in_out_allowed?(_), do: false
defp all_values(struct, fun) do
struct.required
|> Map.values()
|> Kernel.++(Map.values(struct.optional))
|> Enum.map(fun)
|> Enum.all?()
end
@impl true
def binary_size(%{packed: packed}) when is_integer(packed), do: div(packed, 8)
def binary_size(%{extern: extern}) when is_integer(extern), do: div(extern, 8)
def binary_size(_), do: nil
@impl true
def render_cleanup(_, _), do: Type._default_cleanup()
@impl true
def needs_size?(_), do: false
@impl true
def payload_options(_, _), do: Type._default_payload_options()
@impl true
def marshal_param(_, variable, _, platform), do: Type._default_marshal_param(platform, variable)
@impl true
def marshal_return(_, variable, platform), do: Type._default_marshal_return(platform, variable)
end