Macro-generated int signedness binary matching

Hi,

I’ve been stuck with the following problem for some time now.

What I’m trying to achieve is to generate a family of functions for binary matching various integer sizes and signednesses, something like this:

for bits <- [8, 16, 32, 64], {schar, sign} <- [{"i", :signed}, {"u", :unsigned}] do
  def unquote(:"get_#{schar}#{bits}")(bin) do
    <<val::unquote(sign)-unquote(bits), bin::binary>> = bin
    {val, bin}
  end
end

While the size specifier unquote(bits) works, the unquote(sign) fails at compile time with an error “unknown bitstring specifier: :signed”.

Of course I can work around this by “unrolling” the sign — I can def unquote(:"get_i#{bits})" and def unquote(:"get_u#{bits}"), but that does not help me to better understand the language.

So, could anyone with stronger Elixir-fu shed some light on this, please? Is there a way to do this, or is it too much an obscure thing to do?

That’s because the binary AST does not expect atoms but something that looks like a variable or a call. This should work:

for bits <- [8, 16, 32, 64], {schar, sign} <- [{"i", :signed}, {"u", :unsigned}] do
  def unquote(:"get_#{schar}#{bits}")(bin) do
    <<val::unquote(bits)-unquote(sign)(), bin::binary>> = bin
    {val, bin}
  end
end
4 Likes

And it does! I knew I was close, but totally clueless at the same time. Thank you.

<<val::unquote(bits)-unquote(sign)(), bin::binary>>

Wow, had no idea you could do this. Can see that it works, would love to understand why it works! :smiley:

(Disclaimer: I struggle with the explanation. It may thus be either completely wrong or unnecessarily obvious.)

matches {Expr, Meta, Args}, which (as Jose said) is something that looks like a variable or a call. So it is a matter of supplying that something, and it can be done like this:

iex> quote do unquote(:signed)() end
{:signed, [], []}

How does this work? Well, the AST representation of a function call xxx(y, z) is {:xxx, [], [y, z]}. I think that unquote(:signed)() is turned to (something effectively similar to) {{:unquote, [], [:signed]}, [], []}. Next, {:unquote, [], [:signed]} evaluates simply to :signed (as atoms, numbers, bitstrings unquote to themselves), leaving us with {:signed, [], []}.

2 Likes