Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimise unpacking by using single match context #30

Merged
merged 18 commits into from Apr 15, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
257 changes: 158 additions & 99 deletions lib/msgpax/unpacker.ex
@@ -1,25 +1,3 @@
defmodule Msgpax.Unpacker.Unpack do
@moduledoc false

import Macro, only: [pipe: 3]

defmacro defunpack(format, to: value) do
quote do
def unpack(<<unquote_splicing(format), rest::bytes>>, _opts) do
{unquote(value), rest}
end
end
end

defmacro defunpack(format, do: block) do
quote do
def unpack(<<unquote_splicing(format), rest::bytes>>, opts) do
unquote(pipe(quote(do: rest), pipe(quote(do: opts), block, 0), 0))
end
end
end
end

defmodule Msgpax.UnpackError do
@moduledoc """
Raised when there's an error in de-serializing some data into an Elixir term.
Expand All @@ -46,111 +24,192 @@ end
defmodule Msgpax.Unpacker do
@moduledoc false

import __MODULE__.Unpack
def unpack(<<buffer::bits>>, options) do
unpack(buffer, [], options, [], 0, 1)
end

defunpack [0xC0], to: nil
defunpack [0xC2], to: false
defunpack [0xC3], to: true
primitives = %{
[quote(do: [0xC0])] => quote(do: nil),
[quote(do: [0xC2])] => quote(do: false),
[quote(do: [0xC3])] => quote(do: true),
[# Strings
quote(do: [0b101::3, length::5, value::size(length)-bytes]),
quote(do: [0xD9, length::8, value::size(length)-bytes]),
quote(do: [0xDA, length::16, value::size(length)-bytes]),
quote(do: [0xDB, length::32, value::size(length)-bytes]),

# Floats
quote(do: [0xCA, value::32-float]),
quote(do: [0xCB, value::64-float]),

# Integers
quote(do: [0::1, value::7]),
quote(do: [0xCC, value::8]),
quote(do: [0xCD, value::16]),
quote(do: [0xCE, value::32]),
quote(do: [0xCF, value::64]),
quote(do: [0xD0, value::8-signed]),
quote(do: [0xD1, value::16-signed]),
quote(do: [0xD2, value::32-signed]),
quote(do: [0xD3, value::64-signed])] => quote(do: value),
# Negative fixint
[quote(do: [0b111::3, value::5])] => quote(do: value - 0b100000),
}
for {formats, value} <- primitives, format <- formats do
defp unpack(<<unquote_splicing(format), rest::bits>>, result, options, outer, index, count) do
result = [unquote(value) | result]
unpack_continue(rest, result, options, outer, index, count)
end
end

# String
defunpack [0b101::3, len::5, val::size(len)-bytes], to: val
defunpack [0xD9, len::integer, val::size(len)-bytes], to: val
defunpack [0xDA, len::16-integer, val::size(len)-bytes], to: val
defunpack [0xDB, len::32-integer, val::size(len)-bytes], to: val
lists = [
quote(do: [0b1001::4, length::4]),
quote(do: [0xDC, length::16]),
quote(do: [0xDD, length::32]),
]
for format <- lists do
defp unpack(<<unquote_splicing(format), rest::bits>>, result, options, outer, index, count) do
case unquote(quote(do: length)) do
0 ->
unpack_continue(rest, [[] | result], options, outer, index, count)
length ->
unpack(rest, result, options, [:list, index, count | outer], 0, length)
end
end
end

# Binary
defunpack [0xC4, len::integer, val::size(len)-bytes], do: unpack_binary(val)
defunpack [0xC5, len::16-integer, val::size(len)-bytes], do: unpack_binary(val)
defunpack [0xC6, len::32-integer, val::size(len)-bytes], do: unpack_binary(val)
maps = [
quote(do: [0b1000::4, length::4]),
quote(do: [0xDE, length::16]),
quote(do: [0xDF, length::32]),
]
for format <- maps do
defp unpack(<<unquote_splicing(format), rest::bits>>, result, options, outer, index, count) do
case unquote(quote(do: length)) do
0 ->
unpack_continue(rest, [%{} | result], options, outer, index, count)
length ->
unpack(rest, result, options, [:map, index, count | outer], 0, length * 2)
end
end
end

# Float
defunpack [0xCA, val::32-big-float], to: val
defunpack [0xCB, val::64-big-float], to: val
binaries = [
quote(do: [0xC4, length::8, content::size(length)-bytes]),
quote(do: [0xC5, length::16, content::size(length)-bytes]),
quote(do: [0xC6, length::32, content::size(length)-bytes]),
]
for format <- binaries do
defp unpack(<<unquote_splicing(format), rest::bits>>, result, options, outer, index, count) do
value = unpack_binary(unquote(quote(do: content)), options)
unpack_continue(rest, [value | result], options, outer, index, count)
end
end

# Integer
defunpack [0::1, val::7], to: val
defunpack [0xCC, val], to: val
defunpack [0xCD, val::16], to: val
defunpack [0xCE, val::32], to: val
defunpack [0xCF, val::64], to: val
extensions = [
quote(do: [0xD4, type, content::1-bytes]),
quote(do: [0xD5, type, content::2-bytes]),
quote(do: [0xD6, type, content::4-bytes]),
quote(do: [0xD7, type, content::8-bytes]),
quote(do: [0xD8, type, content::16-bytes]),
quote(do: [0xC7, length::8, type, content::size(length)-bytes]),
quote(do: [0xC8, length::16, type, content::size(length)-bytes]),
quote(do: [0xC9, length::32, type, content::size(length)-bytes]),
]
for format <- extensions do
defp unpack(<<unquote_splicing(format), rest::bits>>, result, options, outer, index, count) do
value = unpack_ext(unquote(quote(do: type)), unquote(quote(do: content)), options)
unpack_continue(rest, [value | result], options, outer, index, count)
end
end

defunpack [0b111::3, val::5], to: val - 0b100000
defunpack [0xD0, val::signed-integer], to: val
defunpack [0xD1, val::16-signed-integer], to: val
defunpack [0xD2, val::32-signed-integer], to: val
defunpack [0xD3, val::64-signed-integer], to: val
defp unpack(<<byte, _::bits>>, _result, _options, _outer, _index, _count) do
throw {:bad_format, byte}
end

# Array
defunpack [0b1001::4, len::4], do: unpack_list(len)
defunpack [0xDC, len::16], do: unpack_list(len)
defunpack [0xDD, len::32], do: unpack_list(len)
defp unpack(<<_::bits>>, _result, _options, _outer, _index, _count) do
throw :incomplete
end

# Map
defunpack [0b1000::4, len::4], do: unpack_map(len)
defunpack [0xDE, len::16], do: unpack_map(len)
defunpack [0xDF, len::32], do: unpack_map(len)
@compile {:inline, [unpack_continue: 6]}

# Extension
defunpack [0xD4, type, val::1-bytes], do: unpack_ext(type, val)
defunpack [0xD5, type, val::2-bytes], do: unpack_ext(type, val)
defunpack [0xD6, type, val::4-bytes], do: unpack_ext(type, val)
defunpack [0xD7, type, val::8-bytes], do: unpack_ext(type, val)
defunpack [0xD8, type, val::16-bytes], do: unpack_ext(type, val)
defp unpack_continue(rest, result, options, outer, index, count) do
case index + 1 do
^count ->
unpack_continue(rest, options, outer, result, count)
index ->
unpack(rest, result, options, outer, index, count)
end
end

defunpack [0xC7, len, type, val::size(len)-bytes], do: unpack_ext(type, val)
defunpack [0xC8, len::16, type, val::size(len)-bytes], do: unpack_ext(type, val)
defunpack [0xC9, len::32, type, val::size(len)-bytes], do: unpack_ext(type, val)
defp unpack_continue(<<buffer::bits>>, options, [kind, index, length | outer], result, count) do
result = build_collection(result, count, kind)
case index + 1 do
^length ->
unpack_continue(buffer, options, outer, result, length)
index ->
unpack(buffer, result, options, outer, index, length)
end
end

def unpack(<<bin, _::bytes>>, _opts),
do: throw({:bad_format, bin})
defp unpack_continue(<<buffer::bits>>, _options, [], [value], 1) do
{value, buffer}
end

def unpack(<<_::bits>>, _opts),
do: throw(:incomplete)
defp unpack_binary(content, %{binary: true}) do
Msgpax.Bin.new(content)
end

defp unpack_binary(rest, %{binary: true}, val),
do: {Msgpax.Bin.new(val), rest}
defp unpack_binary(content, _options) do
content
end

defp unpack_binary(rest, _opts, val),
do: {val, rest}
defp unpack_ext(type, content, options) do
if type in 0..127 do
unpack_ext_module(type, content, options)
else
throw {:not_supported_ext, type}
end
end

defp unpack_list(rest, opts, len, acc \\ [])
@compile {:inline, [unpack_ext_module: 3]}

defp unpack_list(rest, _opts, 0, acc),
do: {:lists.reverse(acc), rest}
defp unpack_ext_module(type, content, %{ext: module}) when is_atom(module) do
case module.unpack(Msgpax.Ext.new(type, content)) do
{:ok, result} ->
result
:error ->
throw {:ext_unpack_failure, type, module, content}
end
end

defp unpack_list(rest, opts, len, acc) do
{val, rest} = unpack(rest, opts)
unpack_list(rest, opts, len - 1, [val | acc])
defp unpack_ext_module(type, content, _options) do
Msgpax.Ext.new(type, content)
end

defp unpack_map(rest, opts, len, acc \\ [])
@compile {:inline, [build_collection: 3]}

defp unpack_map(rest, _opts, 0, acc),
do: {:maps.from_list(acc), rest}
defp build_collection(result, count, :list) do
build_list(result, [], count)
end

defp unpack_map(rest, opts, len, acc) do
{key, rest} = unpack(rest, opts)
{val, rest} = unpack(rest, opts)
unpack_map(rest, opts, len - 1, [{key, val} | acc])
defp build_collection(result, count, :map) do
build_map(result, [], count)
end

defp unpack_ext(rest, opts, type, data) when type in 0..127 do
{unpack_ext(type, data, opts), rest}
defp build_list(result, list, 0) do
[list | result]
end

defp unpack_ext(_rest, _opts, type, _data) do
throw {:not_supported_ext, type}
defp build_list([item | rest], list, count) do
build_list(rest, [item | list], count - 1)
end

defp unpack_ext(type, data, %{ext: module}) when is_atom(module) do
case module.unpack(Msgpax.Ext.new(type, data)) do
{:ok, result} -> result
:error ->
throw {:ext_unpack_failure, type, module, data}
end
defp build_map(result, pairs, 0) do
[:maps.from_list(pairs) | result]
end

defp unpack_ext(type, data, _opts) do
Msgpax.Ext.new(type, data)
defp build_map([value, key | rest], pairs, count) do
build_map(rest, [{key, value} | pairs], count - 2)
end
end