Skip to content

Instantly share code, notes, and snippets.

@benfalk
Created December 6, 2017 23:41
Show Gist options
  • Save benfalk/6e5b6c080a97234d9a0e4acb738bd9fd to your computer and use it in GitHub Desktop.
Save benfalk/6e5b6c080a97234d9a0e4acb738bd9fd to your computer and use it in GitHub Desktop.
defmodule Packetx do
@moduledoc """
Documentation for Packetx.
"""
defmacro __using__([do: block]) do
quote do
Module.register_attribute(__MODULE__, :struct_fields, accumulate: true)
Module.register_attribute(__MODULE__, :binary_matches, accumulate: true)
try do
import Packetx
unquote(block)
after
:ok
end
Module.eval_quoted __ENV__, [
Packetx.__defstruct__(@struct_fields),
Packetx.__def_to_binary__(@binary_matches |> Enum.reverse),
Packetx.__def_from_binary__(@binary_matches |> Enum.reverse)
]
end
end
defmacro version(version_string) do
version = Version.parse!(version_string) |> Macro.escape
quote do
def version, do: unquote(version)
end
end
defmacro field(name, type, opts \\ []) do
quote do
Module.put_attribute(__MODULE__, :struct_fields, {unquote(name), nil})
Module.put_attribute(__MODULE__, :binary_matches, {unquote(name), unquote(type), unquote(opts)})
end
end
def __defstruct__(fields) do
quote do
defstruct unquote(Macro.escape(fields))
end
end
def __def_to_binary__(matches) do
Enum.join([
"def to_binary(%__MODULE__{#{to_bin_matches(matches)}}) do",
"<<#{to_bin_binaries(matches)}>>",
"end"], "\n")
|> Code.string_to_quoted!
end
def __def_from_binary__(matches) do
Enum.join([
"def from_binary(<<#{from_bin_matches(matches)}>>) do",
"%__MODULE__{#{to_bin_matches(matches)}}",
"end"], "\n")
|> Code.string_to_quoted!
end
## God Help Me
defp to_bin_matches(binary_matches) do
binary_matches
|> Enum.map(fn {name, _, _} -> "#{name}: #{name}" end)
|> Enum.join(", ")
end
defp to_bin_binaries(binary_matches) do
binary_matches
|> Enum.map(&build_bin/1)
|> Enum.join(", ")
end
defp build_bin({name, :integer, opts}) do
[bits: size] = Keyword.get(opts, :size, [bits: 8])
"#{name}::#{size}"
end
defp build_bin({name, :pascal_string, _}) do
"byte_size(#{name})::8, #{name}::binary"
end
defp from_bin_matches(matches) do
matches
|> Enum.map(&from_bin_match/1)
|> Enum.join(", ")
end
defp from_bin_match({name, :integer, opts}) do
[bits: size] = Keyword.get(opts, :size, [bits: 8])
"#{name}::#{size}"
end
defp from_bin_match({name, :pascal_string, _}) do
"#{name}_len__::8, #{name}::bytes-size(#{name}_len__)"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment