Skip to content

Instantly share code, notes, and snippets.

@betawaffle
Last active December 21, 2015 13:39
Show Gist options
  • Save betawaffle/6314477 to your computer and use it in GitHub Desktop.
Save betawaffle/6314477 to your computer and use it in GitHub Desktop.
defmodule BitString.Example do
import BitString
defbitstring data_frame do
_ :: 1
stream_id :: 31
flags :: 8
length :: 24
_ :: 1
_ :: 7
data :: [binary, size(length)]
end
def stream_id(data_frame(stream_id: stream_id)) do
stream_id
end
def data(data_frame(data: data)) do
data
end
end
defmodule BitString do
alias Elixir.BitString.Field, as: F
def assemble(fields, keyword, caller) do
fields = F.replace(keyword, fields)
fields = F.assemble(fields, caller.in_match?)
quote do: << unquote_splicing(fields) >>
end
defmacro defbitstring(name, do: expr) do
case Macro.extract_args(name) do
{ name, [] } -> nil
_ -> raise ArgumentError, message: "expected a macro name"
end
fields = extract_expressions(expr) |> F.from_expressions
quote do
defmacro unquote(name)(keyword) do
unquote(__MODULE__).assemble(unquote(fields), keyword, __CALLER__)
end
end
end
defmacro defbitstringp(name, do: expr) do
case Macro.extract_args(name) do
{ name, [] } -> nil
_ -> raise ArgumentError, message: "expected a macro name"
end
fields = extract_expressions(expr) |> F.from_expressions
quote do
defmacrop unquote(name)(keyword) do
unquote(__MODULE__).assemble(unquote(fields), keyword, __CALLER__)
end
end
end
defp extract_expressions({ :__block__, _, exprs }), do: exprs
defp extract_expressions(nil), do: []
defp extract_expressions(expr), do: [expr]
end
defrecord BitString.Field, name: :_, default: 0, type: [], order: nil, var: quote(do: _) do
alias __MODULE__, as: F
def assemble(fields, in_match) do
Enum.map(fields, assemble(&1, fields, in_match))
|> Enum.map(fn { body, type } ->
quote(do: unquote(body) :: unquote(type))
end)
end
def from_expressions(exprs) do
Stream.with_index(exprs)
|> Enum.reduce([], &from_expression/2) |> :lists.reverse
|> Macro.escape
end
def replace({ key, val }, fields), do: Enum.map(fields, replace(key, val, &1))
def replace(dict, fields), do: Enum.reduce(dict, fields, &replace/2)
defp assemble(F[var: var, type: type] = field, fields, in_match) do
body = if in_match, do: var, else: default(field)
type = Enum.map(type, replace_vars(&1, fields, in_match))
{ body, type }
end
defp from_expression({ expr, i }, fields) do
field = split(expr, F[order: i])
case field do
F[type: []] -> nil
F[type: type] -> fields = Enum.reduce(type, fields, &require_vars/2)
end
[field|fields]
end
defp lookup_field(_, :_), do: nil
defp lookup_field(fields, key) do
Enum.find_value(fields,
fn F[name: ^key] = field -> field
_ -> nil
end)
end
defp maybe_wrap(list) when is_list(list), do: list
defp maybe_wrap(other), do: [other]
defp replace(key, val, F[name: key] = field), do: default(val, var(val, field))
defp replace(_, _, field), do: field
defp replace_vars({ name, _, [arg] } = call, fields, in_match) when name in [:size, :unit] do
case arg do
{ key, _, _context } when is_atom(key) and is_atom(_context) ->
case lookup_field(fields, key) do
F[var: val] when in_match == true ->
set_elem(call, 2, [val])
F[default: default] ->
set_elem(call, 2, [default])
_ ->
call
end
_ ->
call
end
end
defp replace_vars(other, _, _), do: other
defp require_vars({ name, _, [arg] }, fields) when name in [:size, :unit] do
case arg do
{ key, _, _context } when is_atom(key) and is_atom(_context) ->
Enum.map(fields,
fn F[name: ^key] = field ->
var(arg, field)
field ->
field
end)
_ ->
fields
end
end
defp require_vars(_, fields), do: fields
defp split({ :::, _, [expr, type] }, field), do: split(expr, type(maybe_wrap(type), field))
defp split({ ://, _, [expr, default] }, field), do: split(expr, default(default, field))
defp split({ name, _, _context }, field) when is_atom(name) and is_atom(_context) do
name(name, field)
end
defp split(expr, _) do
raise ArgumentError, message: "expected field, got #{Macro.to_string(expr)}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment