Macro for expressing multiple elements of a bitstrings

I deal with a binary protocol which allows me to send data such as:

<<id::32, x::32, y::32, msg1_length::16, msg1::size(msg1_length), msg2_length::16, msg2::size(msg2_length)>>

Now I would love to save me from this complicated string (msg1, msg2) definition and instead be able to write something like:

<<id::32, x::32, y::32, string(msg1), string(msg2)>>

Is something like this possible? I would need to be able to quote msg1_length::16, msg1::size(msg1_length) from a macro, right? However, I see no way how I could do this since it (by itself) is no valid statement.

1 Like

When writing string/1 as a macro, and playing around with var!/1, it might work when applied to the pattern like this:

<<id::32, x::32, y::32>> <> string(msg1) <> string(msg2)

The macro of course needs to be implemented to return code equivalent to <<length::16, the_var_name::size(length)>>.

Yes, that is something I am playing around with in the meantime, but I hoped to keep the syntax a bit cleaner since I will need to define quite a few of those bitstrings.

Something like this would work:

defmodule M do
  defmacro str(num) do
    {:<<>>, [], 
      [{:::, [], [{:"msg#{num}_length", [], Elixir}, 16]},
       {:::, [], 
         [{:var!, [context: Elixir, import: Kernel], [{:"msg#{num}", [], Elixir}]},
          {:size, [], [{:"msg#{num}_length", [], Elixir}]} ]}
    ]}
  end
end

require M
<<id::32, M.str(1)>> = <<0,0,0,0>> <> <<0,8>> <> "A"
<<0, 0, 0, 0, 0, 8, 65>>
msg1
#⇒ 65                            

It’s up to you to get it back from AST to more readable view :slight_smile: I did quote do: <<msg1_length::16, var!(msg1)::size(msg1_length)>> and copy-pasted the result.

1 Like

Interesting…

Indeed <<1, <<2, 3>>, 4>> = <<1, 2, 3, 4>> is the same. I did not realize that. That makes things simpler!

Thanks!

Here is what I ended up using:

  @doc """
  Macro for generating a match of an string in a bitstream

  ## Examples
      iex> ManaElixir.Networking.Enet.Macros.string(msg) = <<5::16, "Hello">>
      ...> msg
      "Hello"

  """
  defmacro string(name) do
    varname = elem(name, 0) |> Atom.to_string()
    length = :"length_#{varname}" |> Macro.var(nil)
    quote do
      <<unquote(length)::16, unquote(name)::binary-size(unquote(length))>>
    end
  end

It works without var!. I am just not 100% sure about the Macro.var(nil). but it seems to work fine :slight_smile: