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.
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