Current section
Files
Jump to
Current section
Files
lib/zig/type.ex
use Protoss
defprotocol Zig.Type do
@moduledoc false
alias Zig.Type.Array
alias Zig.Type.Bool
alias Zig.Type.Cpointer
alias Zig.Type.Error
alias Zig.Type.Float
alias Zig.Type.Integer
alias Zig.Type.Manypointer
alias Zig.Type.Optional
alias Zig.Type.Pointer
alias Zig.Type.Resource
alias Zig.Type.Slice
alias Zig.Type.Struct
alias Zig.Type.Tuple
@type t ::
Array.t()
| Bool.t()
| Cpointer.t()
| Error.t()
| Zig.Type.Enum.t()
| Float.t()
| Integer.t()
| ManyPointer.t()
| Optional.t()
| Pointer.t()
| Slice.t()
| Struct.t()
| :void
| :anyopaque
| :env
| :pid
| :port
| :term
| :erl_nif_term
| :erl_nif_binary
| :erl_nif_event
| :erl_nif_binary_pointer
| :stacktrace
@type json :: number | String.t() | boolean | nil | [json] | %{optional(String.t()) => json}
@spec marshal_param(t, Macro.t(), non_neg_integer, Elixir | :erlang) :: Macro.t()
def marshal_param(type, variable_ast, index, platform)
@spec marshal_return(t, Macro.t(), Elixir | :erlang) :: Macro.t()
def marshal_return(type, variable_ast, platform)
# validations:
@spec get_allowed?(t) :: boolean
def get_allowed?(type)
@spec make_allowed?(t) :: boolean
def make_allowed?(type)
@spec in_out_allowed?(t) :: boolean
def in_out_allowed?(type)
@spec binary_size(t) :: nil | non_neg_integer | {:var, non_neg_integer}
def binary_size(type)
# rendered zig code:
@spec payload_options(t, String.t()) :: iodata
def payload_options(type, prefix)
@spec render_cleanup(t, non_neg_integer) :: iodata
def render_cleanup(type, index)
@spec needs_size?(t) :: boolean
def needs_size?(type)
@spec render_zig(t) :: String.t()
def render_zig(type)
@spec render_elixir_spec(t, Return.t() | Param.t() | Return.type()) :: Macro.t()
def render_elixir_spec(type, context)
@spec render_erlang_spec(t, Return.t() | Param.t() | Return.type()) :: String.t()
def render_erlang_spec(type, context)
after
defmacro sigil_t({:<<>>, _, [string]}, _) do
string
|> parse
|> Macro.escape()
end
def parse("u" <> _ = string), do: Integer.parse(string)
def parse("i" <> _ = string), do: Integer.parse(string)
def parse("f" <> _ = string), do: Float.parse(string)
def parse("c_uint" <> _ = string), do: Integer.parse(string)
def parse("[]" <> rest), do: Slice.of(parse(rest))
def parse("[:0]" <> rest), do: Slice.of(parse(rest), has_sentinel?: true)
def parse("[*]" <> rest), do: Manypointer.of(parse(rest))
def parse("[*:0]" <> rest), do: Manypointer.of(parse(rest), has_sentinel?: true)
def parse("[*c]" <> rest), do: Cpointer.of(parse(rest))
def parse("?" <> rest), do: Optional.of(parse(rest))
def parse("[" <> maybe_array = string) do
case Elixir.Integer.parse(maybe_array) do
{count, "]" <> rest} -> Array.of(parse(rest), count)
{count, ":0]" <> rest} -> Array.of(parse(rest), count, has_sentinel?: true)
_ -> raise "unknown type #{string}"
end
end
def parse("?*.cimport" <> rest) do
if String.ends_with?(rest, "struct_enif_environment_t") do
Env
else
unknown = rest |> String.split(".") |> List.last()
raise "unknown type #{unknown}"
end
end
# derivative functions
def binary_typespec(type) do
case binary_size(type) do
nil ->
nil
{:indirect, _} ->
nil
{:var, 1} ->
quote context: Elixir do
binary()
end
{:var, size} when size <= 32 ->
quote context: Elixir do
<<_::_*unquote(size * 8)>>
end
{:var, _} ->
quote context: Elixir do
binary()
end
size ->
quote context: Elixir do
<<_::unquote(size * 8)>>
end
end
end
# following two lines cause infinite loop
# @callback from_json(json, term) :: t
# @optional_callbacks [from_json: 2]
# Handle null/nil - only allow during documentation sema passes
def from_json(value, module) when value in [:null, nil] do
if module, do: raise(CompileError, description: "zigler encountered anytype"), else: :anytype
end
# Handle unusable types - only allow during documentation sema passes.
# module is nil only when called from run_sema_doc/1 for zig_doc generation.
def from_json(%{"type" => "unusable:" <> typename}, module) do
if module do
raise CompileError, description: "zigler encountered the unusable type #{typename}"
else
String.to_atom(typename)
end
end
# Simple type mappings
def from_json(%{"type" => "bool"} = json, _module), do: Bool.from_json(json)
def from_json(%{"type" => "void"}, _module), do: :void
def from_json(%{"type" => "integer"} = json, _module), do: Integer.from_json(json)
def from_json(%{"type" => "enum"} = json, module), do: Zig.Type.Enum.from_json(json, module)
def from_json(%{"type" => "float"} = json, _module), do: Float.from_json(json)
def from_json(%{"type" => "resource"} = json, module), do: Resource.from_json(json, module)
# Struct types
def from_json(%{"type" => "struct", "name" => "beam.term"}, _module), do: :term
def from_json(%{"type" => "struct", "is_tuple" => true} = json, module), do: Tuple.from_json(json, module)
def from_json(%{"type" => "struct"} = json, module), do: Struct.from_json(json, module)
# Collection types
def from_json(%{"type" => "array"} = json, module), do: Array.from_json(json, module)
def from_json(%{"type" => "slice"} = json, module), do: Slice.from_json(json, module)
def from_json(%{"type" => "manypointer"} = json, module), do: Manypointer.from_json(json, module)
def from_json(%{"type" => "cpointer"} = json, module), do: Cpointer.from_json(json, module)
def from_json(%{"type" => "error"} = json, module), do: Error.from_json(json, module)
# Pointer types - specific patterns first
def from_json(%{"type" => "pointer", "child" => %{"type" => "e.ErlNifBinary"}}, _module), do: :erl_nif_binary_pointer
def from_json(%{"type" => "pointer", "child" => %{"type" => "builtin.StackTrace"}}, _module), do: :stacktrace
def from_json(%{"type" => "optional", "child" => %{"type" => "pointer"}} = json, module), do: Pointer.from_json(json, module)
def from_json(%{"type" => "pointer"} = json, module), do: Pointer.from_json(json, module)
# Optional (general - after specific optional patterns)
def from_json(%{"type" => "optional"} = json, module), do: Optional.from_json(json, module)
# Beam/NIF types
def from_json(%{"type" => "env"}, _module), do: :env
def from_json(%{"type" => "erl_nif_term"}, _module), do: :erl_nif_term
def from_json(%{"type" => "pid"}, _module), do: :pid
def from_json(%{"type" => "port"}, _module), do: :port
def from_json(%{"type" => "term"}, _module), do: :term
def from_json(%{"type" => "e.ErlNifBinary"}, _module), do: :erl_nif_binary
def from_json(%{"type" => "e.ErlNifEvent"}, _module), do: :erl_nif_event
def from_json(%{"type" => "builtin.StackTrace"}, _module), do: :stacktrace
def needs_make?(:erl_nif_term), do: false
def needs_make?(:term), do: false
def needs_make?(_), do: true
# defaults
def _default_payload_options, do: [error_info: "&error_info"]
def _default_marshal_param(Elixir, _variable), do: []
def _default_marshal_param(:erlang, variable), do: "#{variable}_m = #{variable},"
def _default_marshal_return(Elixir, variable), do: variable
def _default_marshal_return(:erlang, _variable), do: "Return"
def _default_cleanup, do: ".{},"
end
defimpl Zig.Type, for: Atom do
alias Zig.Type
@impl true
def get_allowed?(type), do: type in ~w[term erl_nif_term pid]a
@impl true
def make_allowed?(type), do: type in ~w[term erl_nif_term pid void]a
@impl true
def in_out_allowed?(_), do: false
@impl true
def binary_size(_), do: nil
@impl true
def render_zig(:term), do: "beam.term"
def render_zig(:erl_nif_term), do: "e.erl_nif_term"
def render_zig(:pid), do: "beam.pid"
def render_zig(:env), do: "beam.env"
def render_zig(:anyopaque), do: "anyopaque"
def render_zig(atom), do: "#{atom}"
@impl true
def payload_options(:erl_nif_term, _), do: []
def payload_options(:term, _), do: []
def payload_options(type, _)
when type in ~w[env stacktrace erl_nif_binary erl_nif_event erl_nif_binary_pointer]a,
do: raise("unreachable")
def payload_options(_, _), do: Type._default_payload_options()
@impl true
def render_cleanup(_, _), do: Type._default_cleanup()
@impl true
def needs_size?(_), do: false
@impl true
def render_elixir_spec(:void, %Zig.Return{}), do: :ok
def render_elixir_spec(:pid, _) do
quote do
pid()
end
end
@impl true
def render_elixir_spec(term, _) when term in ~w[term erl_nif_term]a do
quote do
term()
end
end
@impl true
def render_erlang_spec(:void, %Zig.Return{}), do: "ok"
def render_erlang_spec(:pid, _), do: "pid()"
def render_erlang_spec(term, _) when term in ~w[term erl_nif_term]a, do: "term()"
@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