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
Current section
Files
lib/reader.ex
defmodule Eflatbuffers.Reader do
alias Eflatbuffers.Utils
def read({ :bool, _options }, vtable_pointer, data, _) do
case read_from_data_buffer(vtable_pointer, data, 8) do
<< 0 >> -> false
<< 1 >> -> true
end
end
def read({ :byte, _options }, vtable_pointer, data, _) do
<< value :: signed-size(8) >> = read_from_data_buffer(vtable_pointer, data, 8)
value
end
def read({ :ubyte, _options }, vtable_pointer, data, _) do
<< value :: unsigned-size(8) >> = read_from_data_buffer(vtable_pointer, data, 8)
value
end
def read({ :short, _options}, vtable_pointer, data, _) do
<< value :: signed-little-size(16) >> = read_from_data_buffer(vtable_pointer, data, 16)
value
end
def read({ :ushort, _options}, vtable_pointer, data, _) do
<< value :: unsigned-little-size(16) >> = read_from_data_buffer(vtable_pointer, data, 16)
value
end
def read({ :int, _options}, vtable_pointer, data, _) do
<< value :: signed-little-size(32) >> = read_from_data_buffer(vtable_pointer, data, 32)
value
end
def read({ :uint, _options}, vtable_pointer, data, _) do
<< value :: unsigned-little-size(32) >> = read_from_data_buffer(vtable_pointer, data, 32)
value
end
def read({ :float, _options}, vtable_pointer, data, _) do
<< value :: float-little-size(32) >> = read_from_data_buffer(vtable_pointer, data, 32)
value
end
def read({ :long, _options}, vtable_pointer, data, _) do
<< value :: signed-little-size(64) >> = read_from_data_buffer(vtable_pointer, data, 64)
value
end
def read({ :ulong, _options}, vtable_pointer, data, _) do
<< value :: unsigned-little-size(64) >> = read_from_data_buffer(vtable_pointer, data, 64)
value
end
def read({ :double, _options}, vtable_pointer, data, _) do
<< value :: float-little-size(64) >> = read_from_data_buffer(vtable_pointer, data, 64)
value
end
# complex types
def read({ :string, _options}, vtable_pointer, data, _) do
<< string_offset :: unsigned-little-size(32) >> = read_from_data_buffer(vtable_pointer, data, 32)
string_pointer = vtable_pointer + string_offset
<< _ :: binary-size(string_pointer), string_length :: unsigned-little-size(32), string :: binary-size(string_length), _ :: binary >> = data
string
end
def read({:vector, options}, vtable_pointer, data, schema) do
type = options.type
<< vector_offset :: unsigned-little-size(32) >> = read_from_data_buffer(vtable_pointer, data, 32)
vector_pointer = vtable_pointer + vector_offset
<< _ :: binary-size(vector_pointer), vector_count :: unsigned-little-size(32), _ :: binary >> = data
is_scalar = Utils.scalar?(type)
read_vector_elements(type, is_scalar, vector_pointer + 4, vector_count, data, schema)
end
def read({:enum, %{ name: enum_name }}, vtable_pointer, data, {tables, _options} = schema) do
{:enum, options} = Map.get(tables, enum_name)
members = options.members
type = options.type
index = read(type, vtable_pointer, data, schema)
case Map.get(members, index) do
nil ->
throw({:error, {:not_in_enum, index, members}})
value_atom ->
Atom.to_string(value_atom)
end
end
# read a complete table, given a pointer to the springboard
def read({:table, %{ name: table_name }}, table_pointer_pointer, data, {tables, _options} = schema) when is_atom(table_name) do
<< _ :: binary-size(table_pointer_pointer), table_offset :: little-size(32), _ :: binary >> = data
table_pointer = table_pointer_pointer + table_offset
{:table, options} = Map.get(tables, table_name)
fields = options.fields
<< _ :: binary-size(table_pointer), vtable_offset :: little-signed-size(32), _ :: binary >> = data
vtable_pointer = table_pointer - vtable_offset
<< _ :: binary-size(vtable_pointer), vtable_length :: little-size(16), _data_buffer_length :: little-size(16), _ :: binary >> = data
vtable_fields_pointer = vtable_pointer + 4
vtable_fields_length = vtable_length - 4
<< _ :: binary-size(vtable_fields_pointer), vtable :: binary-size(vtable_fields_length), _ :: binary >> = data
data_buffer_pointer = table_pointer
read_table_fields(fields, vtable, data_buffer_pointer, data, schema)
end
# fail if nothing matches
def read({type, _}, _, _, _) do
throw({:error, {:unknown_type, type}})
end
def read_vector_elements(_, _, _, 0, _, _) do
[]
end
def read_vector_elements(type, true, vector_pointer, vector_count, data, schema) do
value = read(type, vector_pointer, data, schema)
offset = Utils.scalar_size(Utils.extract_scalar_type(type, schema))
[value | read_vector_elements(type, true, vector_pointer + offset, vector_count - 1, data, schema)]
end
def read_vector_elements(type, false, vector_pointer, vector_count, data, schema) do
value = read(type, vector_pointer, data, schema)
offset = 4
[value | read_vector_elements(type, false, vector_pointer + offset, vector_count - 1, data, schema)]
end
# this is a utility that just reads data_size bytes from data after data_pointer
def read_from_data_buffer(data_pointer, data, data_size) do
<< _ :: binary-size(data_pointer), value :: bitstring-size(data_size), _ :: binary >> = data
value
end
def read_table_fields(fields, vtable, data_buffer_pointer, data, schema) do
read_table_fields(fields, vtable, data_buffer_pointer, data, schema, %{})
end
# we might still have more fields but we ran out of vtable slots
# this happens if the schema has more fields than the data (schema evolution)
def read_table_fields(_, <<>>, _, _, _, map) do
map
end
# we might have more data but no more fields
# that means the data is ahead and has more data than the schema
def read_table_fields([], _, _, _, _, map) do
map
end
def read_table_fields([{name, {:union, %{ name: union_name }}} | fields], << data_offset :: little-size(16), vtable :: binary >>, data_buffer_pointer, data, {tables, _options} = schema, map) do
# for a union byte field named $fieldname$_type is prefixed
union_index = read({ :byte, %{ default: 0 }}, data_buffer_pointer + data_offset, data, schema)
case union_index do
0 ->
# index is null, so field is not set
# carry on
read_table_fields(fields, vtable, data_buffer_pointer, data, schema, map)
_ ->
# we have a table set so we get the type and
# expect it as the next record in the vtable
{:union, options} = Map.get(tables, union_name)
members = options.members
union_type = Map.get(members, union_index - 1)
union_type_key = String.to_atom(Atom.to_string(name) <> "_type")
map_new = Map.put(map, union_type_key, Atom.to_string(union_type))
read_table_fields([{name, {:table, %{ name: union_type }}} | fields], vtable, data_buffer_pointer, data, schema, map_new)
end
end
# we find a null pointer
# so we set the dafault
def read_table_fields([{name, {:enum, options }} | fields], << 0, 0, vtable :: binary >>, data_buffer_pointer, data, {tables, _} = schema, map) do
{_, enum_options} = Map.get(tables, options.name)
{_, %{ default: default }} = enum_options.type
map_new = Map.put(map, name, Atom.to_string(Map.get(enum_options.members, default)))
read_table_fields(fields, vtable, data_buffer_pointer, data, schema, map_new)
end
def read_table_fields([{name, { _type, options }} | fields], << 0, 0, vtable :: binary >>, data_buffer_pointer, data, schema, map) do
map_new =
case Map.get(options, :default) do
nil -> map
default -> Map.put(map, name, default)
end
read_table_fields(fields, vtable, data_buffer_pointer, data, schema, map_new)
end
def read_table_fields([{name, type} | fields], << data_offset :: little-size(16), vtable :: binary >>, data_buffer_pointer, data, schema, map) do
value = read(type, data_buffer_pointer + data_offset, data, schema)
map_new = Map.put(map, name, value)
read_table_fields(fields, vtable, data_buffer_pointer, data, schema, map_new)
end
end