Packages

This is a flatbuffers implementation in Elixir. In contrast to existing implementations there is no need to compile code from a schema. Instead, data and schemas are processed dynamically at runtime, offering greater flexibility.

Current section

Files

Jump to
eflatbuffers lib schema.ex
Raw

lib/schema.ex

defmodule Eflatbuffers.Schema do
@referenced_types [
:string,
:byte, :ubyte, :bool,
:short, :ushort,
:int, :uint, :float,
:long, :ulong, :double,
]
def parse!(schema_str) do
case parse(schema_str) do
{:ok, schema} ->
schema
{:error, error} ->
throw({:error, error})
end
end
def parse(schema_str) when is_binary(schema_str) do
tokens = lexer(schema_str)
case :schema_parser.parse(tokens) do
{:ok, data} ->
{:ok, decorate(data)}
error ->
error
end
end
def lexer(schema_str) do
{:ok, tokens, _} =
to_charlist(schema_str)
|> :schema_lexer.string
tokens
end
# this preprocesses the schema
# in order to keep the read/write
# code as simple as possible
# correlate tables with names
# and define defaults explicitly
def decorate({entities, options}) do
entities_decorated =
Enum.reduce(
entities,
%{},
# for a tables we transform
# the types to explicitly signify
# vectors, tables, and enums
fn({key, {:table, fields}}, acc) ->
Map.put(
acc,
key,
{ :table, table_options(fields, entities) }
)
# for enums we change the list of options
# into a map for faster lookup when
# writing and reading
({key, {{:enum, type}, fields}}, acc) ->
hash = Enum.reduce(
Enum.with_index(fields),
%{},
fn({field, index}, hash_acc) ->
Map.put(hash_acc, field, index) |> Map.put(index, field)
end
)
Map.put(acc, key, {:enum, %{ type: {type, %{ default: 0 }}, members: hash }})
({key, {:union, fields}}, acc) ->
hash = Enum.reduce(
Enum.with_index(fields),
%{},
fn({field, index}, hash_acc) ->
Map.put(hash_acc, field, index) |> Map.put(index, field)
end
)
Map.put(acc, key, {:union, %{ members: hash }})
end
)
{entities_decorated, options}
end
def table_options(fields, entities) do
fields_and_indices(fields, entities, {0, [], %{}})
end
def fields_and_indices([], _, {_, fields, indices}) do
%{ fields: Enum.reverse(fields), indices: indices }
end
def fields_and_indices([{field_name, field_value} | fields], entities, {index, fields_acc, indices_acc}) do
index_offset = index_offset(field_value, entities)
decorated_type = decorate_field(field_value, entities)
index_new = index + index_offset
fields_acc_new = [{field_name, decorated_type} | fields_acc]
indices_acc_new = Map.put(indices_acc, field_name, {index, decorated_type})
fields_and_indices(fields, entities, {index_new, fields_acc_new, indices_acc_new})
end
def index_offset(field_value, entities) do
case is_referenced?(field_value) do
true ->
case Map.get(entities, field_value) do
{:union, _} ->
2
_ ->
1
end
false ->
1
end
end
def decorate_field({:vector, type}, entities) do
{:vector, %{ type: decorate_field(type, entities) }}
end
def decorate_field(field_value, entities) do
case is_referenced?(field_value) do
true ->
decorate_referenced_field(field_value, entities)
false ->
decorate_field(field_value)
end
end
def decorate_referenced_field(field_value, entities) do
case Map.get(entities, field_value) do
nil ->
throw({:error, {:entity_not_found, field_value}})
{:table, _} ->
{:table, %{ name: field_value }}
{{:enum, _}, _} ->
{:enum, %{ name: field_value }}
{:union, _} ->
{:union, %{ name: field_value }}
end
end
def decorate_field({type, default}) do
{type, %{ default: default} }
end
def decorate_field(:bool) do
{:bool, %{ default: false }}
end
def decorate_field(:string) do
{:string, %{}}
end
def decorate_field(type) do
{type, %{ default: 0 }}
end
def is_referenced?({type, _default}) do
is_referenced?(type)
end
def is_referenced?(type) do
not Enum.member?(@referenced_types, type)
end
end