用于打包和解包任意结构和位串的 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
我正在尝试编写一个宏来节省手动输入数据结构的 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