Last active
December 21, 2015 13:39
-
-
Save betawaffle/6314477 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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