用于打包和解包任意结构和位串的 Elixir 宏

Elixir macros to pack and unpack arbitrary structures and bitstrings

我正在尝试编写一个宏来节省手动输入数据结构的 serialization/deserialization 函数。

目标是能够调用宏

Macros.implement_message(TheMessage, {:field_1, 0, unsigned-size(8)}, {:field_2, 0, unsigned-size(32)})

并在宏调用站点生成看起来像这样的代码:

defmodule TheMessage do
  defstruct [
    field_1: 0,
    field_2, 0,
  ]

  def serialize(%TheMessage{field_1: field_1, field_2: field_2}) do
    <<field_1::unsigned-size(8), field_2::unsigned-size(32)>>
  end

  def deserialize(<<field_1::unsigned-size(8), field_2::unsigned-size(32)>>) do
    %TheMessage{field_1: field_1, field_2: field_2}
  end
end

这就是我得到的,需要一些帮助来填写序列化和反序列化函数:

defmodule Macros do
  @moduledoc false
  @doc """
  Implement a serializable structure, including serialize and a deserialize functions. This
  will defmodule the module, defstruct the fields and their defaults, and generate serialize
  and deserialize functions that function-head-match the module.

    defmodule MyModule do
      Macros.implement_message(TheMessage, [
        {:field_1, 0, unsigned-size(8)},
        {:field_2, 0, unsigned-size(32)},
        ])
    end

    # Equivalent code:
    defmodule MyModule do
      defmodule TheMessage do
        defstruct [
          field_1: 0,
          field_2, 0,
        ]

        def serialize(%MyModule{field_1: field_1, field_2: field_2}) do
          <<field_1::unsigned-size(8), field_2::unsigned-size(32)>>
        end

        def deserialize(<<field_1::unsigned-size(8), field_2::unsigned-size(32)>>) do
          %MyModule{field_1: field_1, field_2: field_2}
        end
      end
    end

  ## Parameters
  - module The module atom
  - field_tuples [{field_atom, default_value, bin_rep}]

  """
  defmacro implement_message(module, field_tuples) do
    module_def_kwl = Enum.map(field_tuples, fn({field_atom, default_value, _bin_rep}) -> {field_atom, default_value} end)
    fields_assignments = Enum.map(module_def_kwl, fn({field_atom, _default_value}) -> {field_atom, Atom.to_string(field_atom)} end)

    ast = quote do
      defmodule unquote(module) do
        defstruct unquote(module_def_kwl)

        def serialize(%unquote(module){unquote(fields_assignments)}) do
          # ?
        end
        def deserialize(_how_to_pattern_match?) do
          # ?
        end
      end
    end

    [ast]
  end
end

关键是使用unquote_splicing and Macro.var

defmodule Serializer do

  defmacro defserialize(_, fields) do
    map_fields =
      fields
      |> Keyword.keys()
      |> Enum.map(&{&1, Macro.var(&1, nil)})

    binary_fields =
      fields
      |> Enum.map(fn {name, bits} ->
        var_name = Macro.var(name, nil)

        quote do
          unquote(var_name) :: unquote(bits)
        end
      end)

    quote do
      def serialize(%{unquote_splicing(map_fields)}) do
        <<unquote_splicing(binary_fields)>>
      end

      def deserialize(<<unquote_splicing(binary_fields)>>) do
        %{unquote_splicing(map_fields)}
      end
    end
  end

end

defmodule Foo do
  import Serializer

  defserialize Bar, [
    x: 4,
    y: 5,
    z: 7
  ]
end