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

问题描述

我试图编写一个宏来保存用于数据结构的手动键入序列化/反序列化功能

目标是能够调用

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

并在宏调用站点生成如下所示的代码

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

  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,unsigned-size(32)},])
    end

    # Equivalent code:
    defmodule MyModule do
      defmodule TheMessage do
        defstruct [
          field_1: 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,_bin_rep}) -> {field_atom,default_value} end)
    fields_assignments = Enum.map(module_def_kwl,_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_splicingMacro.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