defmodule Zig.Type.Integer do use Zig.Type, inspect?: true defstruct [:signedness, :bits] @type t :: %__MODULE__{ signedness: :unsigned | :signed, bits: 0..65_535 | :big } def parse("u" <> number_str = t) do %__MODULE__{signedness: :unsigned, bits: get_bits!(number_str, t)} end def parse("i" <> number_str = t) do %__MODULE__{signedness: :signed, bits: get_bits!(number_str, t)} end def parse(other), do: raise(Zig.Type.ParseError, source: other) @signs %{"signed" => :signed, "unsigned" => :unsigned} def from_json(%{"signedness" => s, "bits" => b}) do %__MODULE__{signedness: Map.fetch!(@signs, s), bits: b} end def get_bits!(number_str, full_type) do case Integer.parse(number_str) do {n, ""} when n in 0..65_535 -> n {n, ""} -> raise Zig.Type.ParseError, source: full_type, reason: "#{n} is out of range of zig bit lengths" _ -> raise Zig.Type.ParseError, source: full_type end end def to_string(integer) do case integer.signedness do :unsigned -> "u#{integer.bits}" :signed -> "i#{integer.bits}" end end def to_call(integer), do: to_string(integer) def inspect(type, _opts) do import Inspect.Algebra concat(["~t(", to_string(type), ")"]) end def marshals_param?(%{bits: bits}), do: bits > 64 def marshals_return?(%{bits: bits}), do: bits > 64 def marshal_param(type, variable, index, :elixir) do marshal_param_elixir(type, variable, index) end def marshal_param(type, variable, index, :erlang) do marshal_param_erlang(type, variable, index) end defp marshal_param_elixir(type, variable, index) do max = typemax(type) min = typemin(type) guards = quote bind_quoted: [ variable: variable, name: to_string(type), index: index, max: max, min: min ] do unless is_integer(variable) do :erlang.error( {:argument_error, index, [{"expected: integer (for `#{name}`)"}, {"got: `#{inspect(variable)}`"}]} ) end unless variable >= min do :erlang.error( {:argument_error, index, [ {"expected: integer (for `#{name}`)"}, {"got: `#{inspect(variable)}`"}, {"note: out of bounds (#{min}..#{max})"} ]} ) end unless variable <= max do :erlang.error( {:argument_error, index, [ {"expected: integer (for `#{name}`)"}, {"got: `#{inspect(variable)}`"}, {"note: out of bounds (#{min}..#{max})"} ]} ) end end size = _next_power_of_two_ceil(type.bits) case type.signedness do :unsigned -> quote do unquote(guards) unquote(variable) = <> end :signed -> quote do unquote(guards) unquote(variable) = <> end end end defp marshal_param_erlang(type, variable, _index) do max = typemax(type) min = typemin(type) size = _next_power_of_two_ceil(type.bits) """ #{variable}_m = case #{variable} of X when not is_integer(X) -> erlang:error(badarg); X when X < #{min} -> erlang:error(badarg); X when X > #{max} -> erlang:error(badarg); X -> <> end, """ end def marshal_return(type, variable, :elixir) do marshal_return_elixir(type, variable) end def marshal_return(type, variable, :erlang) do marshal_return_erlang(type, variable) end defp marshal_return_elixir(type, variable) do size = _next_power_of_two_ceil(type.bits) case type.signedness do :unsigned -> quote do <> = unquote(variable) result end :signed -> quote do <> = unquote(variable) result end end end defp marshal_return_erlang(type, variable) do size = _next_power_of_two_ceil(type.bits) """ <> = #{variable}, NumberResult """ end def _next_power_of_two_ceil(bits), do: _next_power_of_two_ceil(bits, 1, true) defp _next_power_of_two_ceil(bits, so_far, all_zeros) do import Bitwise shifted = bits >>> 1 case {shifted, all_zeros} do {1, true} -> 1 <<< so_far {1, false} -> 2 <<< so_far _ -> all_zeros = all_zeros && (bits &&& 1) == 0 _next_power_of_two_ceil(shifted, so_far + 1, all_zeros) end end def spec(%{bits: 0}, _, _), do: 0 def spec(%{signedness: :unsigned} = type, _, _opts) do quote context: Elixir do 0..unquote(typemax(type)) end end def spec(%{signedness: :signed} = type, _, _opts) do neg_typemin = -typemin(type) quote context: Elixir do -unquote(neg_typemin)..unquote(typemax(type)) end end # note that we're currently not using this function for unsigned ints, which we know is zero defp typemin(%{signedness: :signed, bits: 0}), do: 0 defp typemin(%{signedness: :signed, bits: bits}), do: -Bitwise.<<<(1, bits - 1) defp typemin(%{signedness: :unsigned}), do: 0 defp typemax(%{bits: 0}), do: 0 defp typemax(%{signedness: :unsigned, bits: bits}), do: Bitwise.<<<(1, bits) - 1 defp typemax(%{signedness: :signed, bits: 1}), do: 0 defp typemax(%{signedness: :signed, bits: bits}), do: Bitwise.<<<(1, bits - 1) - 1 def return_allowed?(_), do: true end