Current section
Files
Jump to
Current section
Files
lib/type/container.ex
defmodule ProtoDef.Type.Container do
alias ProtoDef.Compiler.Preprocess
alias ProtoDef.Compiler.AssignIdents
use ProtoDef.Type
@name_and_anon_unset_error "Either name or anon must be set on a container item"
@name_and_anon_set_error "Both name and anon was set on a container item"
@no_type_error "Container item was without a type field"
defstruct items: [], ident: nil
# Preprocess pass
def preprocess(args, ctx) do
types = Enum.map(args, &(preprocess_item(&1, ctx)))
%__MODULE__{
items: types,
}
end
defp preprocess_item(item, ctx) do
name = Preprocess.process_get_arg(item, ctx, "name")
name = item["name"]
anon = item["anon"] == true
type = item["type"]
if !name && !anon do
raise ProtoDef.CompileError, message: @name_and_anon_unset_error
end
if name && anon do
raise ProtoDef.CompileError, message: @name_and_anon_set_error
end
if !type do
raise ProtoDef.CompileError, message: @no_type_error
end
if name == "nil" do
raise ProtoDef.CompileError, message: "Keys can't be named nil"
end
%{
anon: anon,
name: (if name, do: ctx.parse_name.(name)),
type: Preprocess.process_type(type, ctx),
ident: nil,
}
end
# Struct pass
def structure(type, ctx) do
type.items
|> Enum.flat_map(&(structure_field(&1, ctx)))
|> Enum.into(%{})
end
def structure_field(item = %{anon: true}, ctx) do
fields = ProtoDef.Compiler.Structure.gen_for_type(item.type, ctx)
true = is_map(fields)
Enum.into(fields, [])
end
def structure_field(item = %{anon: false}, ctx) do
field = ProtoDef.Compiler.Structure.gen_for_type(item.type, ctx)
[{item.name, field}]
end
# Assign pass
def assign_vars(descr, num, ctx) do
{items, num} = Enum.reduce(descr.items, {[], num}, fn(item, {items, num}) ->
{item, num} = assign_field(item, num, ctx)
{[item | items], num}
end)
items = items |> Enum.reverse
{ident, num} = AssignIdents.make_ident(num, ctx)
descr = %{ descr |
items: items,
ident: ident,
}
{descr, ident, num}
end
def assign_field(item, num, ctx) do
{descr, ident, num} = AssignIdents.assign(item.type, num, ctx)
item = %{ item |
ident: ident,
type: descr,
}
{item, num}
end
# Resolve pass
def resolve_references(descr, parents, ctx) do
parents = [descr.ident | parents]
items = Enum.map(descr.items, &(resolve_field(&1, parents, ctx)))
%{ descr |
items: items,
}
end
def resolve_field(item, parents, ctx) do
type = ProtoDef.Compiler.Resolve.run(item.type, parents, ctx)
%{ item |
type: type,
}
end
# Decoder AST pass
def decoder_ast(descr, ctx) do
var = Macro.var(descr.ident, nil)
fields = Enum.map(descr.items, &item_decoder_ast(&1, var, ctx))
quote do
with do
unquote(var) = %{}
unquote_splicing(ProtoDef.AstUtils.merge_blocks(fields))
{unquote(var), unquote(@data_var)}
end
end
end
def item_decoder_ast(%{anon: true} = item, var, ctx) do
item_decoder = ProtoDef.Compiler.GenAst.decoder(item.type, ctx)
quote do
{field_val, unquote(@data_var)} = unquote(item_decoder)
unquote(var) = Map.merge(unquote(var), field_val)
end
end
def item_decoder_ast(%{anon: false} = item, var, ctx) do
item_decoder = ProtoDef.Compiler.GenAst.decoder(item.type, ctx)
quote do
{field_val, unquote(@data_var)} = unquote(item_decoder)
unquote(var) = Map.put(unquote(var), unquote(item.name), field_val)
end
end
# Encoder AST pass
def encoder_ast(descr, ctx) do
var = Macro.var(descr.ident, nil)
fields = Enum.map(descr.items, &item_encoder_ast(&1, var, ctx))
quote do
with do
result = []
unquote(var) = unquote(@input_var)
unquote_splicing(ProtoDef.AstUtils.merge_blocks(fields))
result
end
end
end
def item_encoder_ast(%{anon: true} = item, var, ctx) do
item_encoder = ProtoDef.Compiler.GenAst.encoder(item.type, ctx)
quote do
unquote(@input_var) = unquote(var)
result = [result, unquote(item_encoder)]
end
end
def item_encoder_ast(%{anon: false} = item, var, ctx) do
item_encoder = ProtoDef.Compiler.GenAst.encoder(item.type, ctx)
quote do
unquote(@input_var) = unquote(var)[unquote(item.name)]
result = [result, unquote(item_encoder)]
end
end
end