1

I am working on a project in which I am likely to be writing a lot of code of the form:

defmodule Kind
  defstruct [name1, name2, name3]
  @type t :: %Kind{name1: integer(), name2: integer(), name3: binary()}
  def unpack(input) do
    with <<name1::integer-8>> <- Enum.take(input, 1),
      <<name2::integer-little-32>> <- Enum.take(input, 4),
      <<name3::binary-10>> <- Enum.take(input, 10),
    do: %Kind{name1: name1, name2: name2, name3: name3>>
  end
end

(for arbitrary sets of input names and types, input being a binary stream producing one byte at a time)

It would be very useful to be able to handle this in a macro, so that I could simply write (for example) use Unpack quote([{name1, integer-8}, {name2, integer-little-32}, {name3, binary-10}]) and automatically generate the necessary struct, typedef, and unpacking function, for arbitrary named fields of fixed sizes. Could even expand it, add a third field in the tuples to pass a function to handle variably-sized types. Unfortunately, when I try to do a simpler version of that (only taking one sized field, and only matching 1 to it):

defmodule Unpack do
  defmacro testmacro({name, kind}) do
    quote do
      <<unquote(name)::unqote(kind)>> = 1
    end
  end
end

The system tells me it's got invalid arguments for quote/1. I assume this is because the "types" used in bitstring pattern-matching are a special form, as are bitstring literals in general, and those particular items are not used anywhere else.

So, how do I get around that? I've got more than a dozen kinds of packed struct to unpack, each with between five and twenty different fields. If I don't do this I'll probably resort to Vim macros to at least save my hands... but that won't really help with having large amounts of extremely repetitive code to maintain.

Vivian
  • 1,539
  • 14
  • 38
  • 1
    Can you post the complete code that gives that error? The second snippet works for me if I fix the typo (unqote) and use a binary on the RHS (you can't match that pattern with an integer). – Dogbert Dec 13 '17 at 02:47
  • ...No, that code was copypasted directly from the iex session I tested it in. Apparently, I missed a typo. Well, that's rather embarrassing. Deleting. – Vivian Dec 13 '17 at 03:06
  • @Dogbert It occurs to me that even if the issue was small, you still answered it. Go ahead, put it in as a response and I'll approve. – Vivian Dec 13 '17 at 15:10

1 Answers1

1

Two things: you have a typo in unquote and the RHS must be a binary so that the pattern matches. With those changes, your code works for me:

defmodule Unpack do
  defmacro unpack({name, kind}) do
    quote do
      <<unquote(name)::unquote(kind)>> = "a"
    end
  end
end

defmodule Main do
  import Unpack

  def main do
    unpack({foo, integer-8})
    IO.inspect foo
  end
end

Main.main

Output:

97
Dogbert
  • 212,659
  • 41
  • 396
  • 397