Packages

🐝 bencode in Gleam

Current section

Files

Jump to
beencode src beencode.erl
Raw

src/beencode.erl

-module(beencode).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([encode/1, decode/1]).
-export_type([b_value/0, b_decode_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type b_value() :: {b_string, bitstring()} |
{b_int, integer()} |
{b_list, list(b_value())} |
{b_dict, gleam@dict:dict(bitstring(), b_value())}.
-type b_decode_error() :: expecting_eof |
unexpected_eof |
{unexpected_char, integer()} |
{empty_number, integer()} |
{negative_zero, integer()} |
{leading_zero, integer()} |
{invalid_dict_key, integer()} |
{string_shorter_than_expected, integer()}.
-file("/Users/giacomocavalieri/Documents/progetti/bvalue/src/beencode.gleam", 225).
-spec decode_string(bitstring(), integer(), integer()) -> {ok,
{b_value(), bitstring(), integer()}} |
{error, b_decode_error()}.
decode_string(String, Acc, Byte_index) ->
case String of
<<"0"/utf8, Rest/bitstring>> ->
decode_string(Rest, Acc * 10, Byte_index + 1);
<<"1"/utf8, Rest@1/bitstring>> ->
decode_string(Rest@1, (Acc * 10) + 1, Byte_index + 1);
<<"2"/utf8, Rest@2/bitstring>> ->
decode_string(Rest@2, (Acc * 10) + 2, Byte_index + 1);
<<"3"/utf8, Rest@3/bitstring>> ->
decode_string(Rest@3, (Acc * 10) + 3, Byte_index + 1);
<<"4"/utf8, Rest@4/bitstring>> ->
decode_string(Rest@4, (Acc * 10) + 4, Byte_index + 1);
<<"5"/utf8, Rest@5/bitstring>> ->
decode_string(Rest@5, (Acc * 10) + 5, Byte_index + 1);
<<"6"/utf8, Rest@6/bitstring>> ->
decode_string(Rest@6, (Acc * 10) + 6, Byte_index + 1);
<<"7"/utf8, Rest@7/bitstring>> ->
decode_string(Rest@7, (Acc * 10) + 7, Byte_index + 1);
<<"8"/utf8, Rest@8/bitstring>> ->
decode_string(Rest@8, (Acc * 10) + 8, Byte_index + 1);
<<"9"/utf8, Rest@9/bitstring>> ->
decode_string(Rest@9, (Acc * 10) + 9, Byte_index + 1);
<<":"/utf8, Rest@10/bitstring>> ->
case Rest@10 of
<<String@1:Acc/binary, Rest@11/bitstring>> ->
{ok,
{{b_string, String@1}, Rest@11, (Byte_index + 1) + Acc}};
_ ->
{error, {string_shorter_than_expected, Byte_index}}
end;
<<>> ->
{error, unexpected_eof};
_ ->
{error, {unexpected_char, Byte_index}}
end.
-file("/Users/giacomocavalieri/Documents/progetti/bvalue/src/beencode.gleam", 278).
-spec decode_int(bitstring(), integer(), boolean(), integer()) -> {ok,
{b_value(), bitstring(), integer()}} |
{error, b_decode_error()}.
decode_int(String, Acc, Negative, Byte_index) ->
case String of
<<"e"/utf8, Rest/bitstring>> when Negative ->
{ok, {{b_int, - Acc}, Rest, Byte_index + 1}};
<<"e"/utf8, Rest@1/bitstring>> ->
{ok, {{b_int, Acc}, Rest@1, Byte_index + 1}};
<<"0"/utf8, Rest@2/bitstring>> ->
decode_int(Rest@2, Acc * 10, Negative, Byte_index + 1);
<<"1"/utf8, Rest@3/bitstring>> ->
decode_int(Rest@3, (Acc * 10) + 1, Negative, Byte_index + 1);
<<"2"/utf8, Rest@4/bitstring>> ->
decode_int(Rest@4, (Acc * 10) + 2, Negative, Byte_index + 1);
<<"3"/utf8, Rest@5/bitstring>> ->
decode_int(Rest@5, (Acc * 10) + 3, Negative, Byte_index + 1);
<<"4"/utf8, Rest@6/bitstring>> ->
decode_int(Rest@6, (Acc * 10) + 4, Negative, Byte_index + 1);
<<"5"/utf8, Rest@7/bitstring>> ->
decode_int(Rest@7, (Acc * 10) + 5, Negative, Byte_index + 1);
<<"6"/utf8, Rest@8/bitstring>> ->
decode_int(Rest@8, (Acc * 10) + 6, Negative, Byte_index + 1);
<<"7"/utf8, Rest@9/bitstring>> ->
decode_int(Rest@9, (Acc * 10) + 7, Negative, Byte_index + 1);
<<"8"/utf8, Rest@10/bitstring>> ->
decode_int(Rest@10, (Acc * 10) + 8, Negative, Byte_index + 1);
<<"9"/utf8, Rest@11/bitstring>> ->
decode_int(Rest@11, (Acc * 10) + 9, Negative, Byte_index + 1);
<<>> ->
{error, unexpected_eof};
_ ->
{error, {unexpected_char, Byte_index}}
end.
-file("/Users/giacomocavalieri/Documents/progetti/bvalue/src/beencode.gleam", 400).
?DOC(
" Encodes a `BValue` into a [bencoded](https://en.wikipedia.org/wiki/Bencode)\n"
" `BitArray`.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" encode(BInt(1))\n"
" // -> <<\"i1e\":utf8>>\n"
"\n"
" encode(BString(\"wibble\"))\n"
" // -> <<\"6:wibble\":utf8>>\n"
"\n"
" encode(BList([BInt(1), BString(\"wibble\")]))\n"
" // -> <<\"li1e6:wibblee\":utf8>>\n"
"\n"
" encode(BDict(dict.from_list([#(BString(\"wibble\"), BInt(1))])))\n"
" // -> <<\"d6:wibblei1ee\">>\n"
" ```\n"
).
-spec encode(b_value()) -> bitstring().
encode(Value) ->
case Value of
{b_int, N} ->
<<"i"/utf8, (erlang:integer_to_binary(N))/binary, "e"/utf8>>;
{b_string, Value@1} ->
Bytes = erlang:byte_size(Value@1),
<<(erlang:integer_to_binary(Bytes))/binary,
":"/utf8,
Value@1/bitstring>>;
{b_list, Values} ->
Encoded_values = gleam@list:map(Values, fun encode/1),
<<"l"/utf8,
(gleam_stdlib:bit_array_concat(Encoded_values))/bitstring,
"e"/utf8>>;
{b_dict, Dict} ->
Values@1 = begin
_pipe = maps:to_list(Dict),
_pipe@1 = gleam@list:sort(
_pipe,
fun(One, Other) ->
{One_key, _} = One,
{Other_key, _} = Other,
gleam@bit_array:compare(One_key, Other_key)
end
),
gleam@list:map(
_pipe@1,
fun(Pair) ->
{Key, Value@2} = Pair,
<<(encode({b_string, Key}))/bitstring,
(encode(Value@2))/bitstring>>
end
)
end,
<<"d"/utf8,
(gleam_stdlib:bit_array_concat(Values@1))/bitstring,
"e"/utf8>>
end.
-file("/Users/giacomocavalieri/Documents/progetti/bvalue/src/beencode.gleam", 323).
-spec decode_list(bitstring(), list(b_value()), integer()) -> {ok,
{b_value(), bitstring(), integer()}} |
{error, b_decode_error()}.
decode_list(String, Acc, Byte_index) ->
case String of
<<"e"/utf8, Rest/bitstring>> ->
{ok, {{b_list, lists:reverse(Acc)}, Rest, Byte_index + 1}};
_ ->
case decode_value(String, Byte_index) of
{error, Error} ->
{error, Error};
{ok, {Value, Rest@1, Byte_index@1}} ->
decode_list(Rest@1, [Value | Acc], Byte_index@1)
end
end.
-file("/Users/giacomocavalieri/Documents/progetti/bvalue/src/beencode.gleam", 146).
-spec decode_value(bitstring(), integer()) -> {ok,
{b_value(), bitstring(), integer()}} |
{error, b_decode_error()}.
decode_value(String, Byte_index) ->
case String of
<<"ie"/utf8, _/bitstring>> ->
{error, {empty_number, Byte_index}};
<<"i0e"/utf8, Rest/bitstring>> ->
{ok, {{b_int, 0}, Rest, Byte_index + 1}};
<<"i-0e"/utf8, _/bitstring>> ->
{error, {negative_zero, Byte_index}};
<<"i0"/utf8, _/bitstring>> ->
{error, {leading_zero, Byte_index}};
<<"i-0"/utf8, _/bitstring>> ->
{error, {leading_zero, Byte_index}};
<<"i-"/utf8, Rest@1/bitstring>> ->
decode_int(Rest@1, 0, true, Byte_index);
<<"i"/utf8, Rest@2/bitstring>> ->
decode_int(Rest@2, 0, false, Byte_index);
<<"l"/utf8, Rest@3/bitstring>> ->
decode_list(Rest@3, [], Byte_index);
<<"d"/utf8, Rest@4/bitstring>> ->
decode_dict(Rest@4, maps:new(), Byte_index);
<<"0:"/utf8, Rest@5/bitstring>> ->
{ok, {{b_string, <<>>}, Rest@5, Byte_index + 2}};
<<"0"/utf8, _/bitstring>> ->
{error, {unexpected_char, Byte_index}};
<<"1"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<"2"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<"3"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<"4"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<"5"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<"6"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<"7"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<"8"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<"9"/utf8, _/bitstring>> ->
decode_string(String, 0, Byte_index + 1);
<<>> ->
{error, unexpected_eof};
_ ->
{error, {unexpected_char, Byte_index}}
end.
-file("/Users/giacomocavalieri/Documents/progetti/bvalue/src/beencode.gleam", 123).
?DOC(
" Decodes a [bencoded](https://en.wikipedia.org/wiki/Bencode) `BitArray` into\n"
" a `BValue`.\n"
"\n"
" > ⚠️ According to the bencode specification the keys of a dictionary should\n"
" > always be sorted lexicographically. This decoder is a bit more permissive\n"
" > and will successfully decode a dictionary even if its keys appear in a\n"
" > different order.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" decode(<<\"i1e\":utf8>>)\n"
" // -> BInt(1)\n"
"\n"
" decode(<<\"6:wibble\":utf8>>)\n"
" // -> BString(\"wibble\")\n"
"\n"
" decode(<<\"li1e6:wibblee\":utf8>>)\n"
" // -> BList([BInt(1), BString(\"wibble\")])\n"
"\n"
" decode(<<\"d6:wibblei1ee\">>)\n"
" // -> BDict(dict.from_list([#(BString(\"wibble\"), BInt(1))]))\n"
" ```\n"
).
-spec decode(bitstring()) -> {ok, b_value()} | {error, b_decode_error()}.
decode(Input) ->
case decode_value(Input, 0) of
{ok, {Value, <<>>, _}} ->
{ok, Value};
{ok, {_, _, _}} ->
{error, expecting_eof};
{error, Reason} ->
{error, Reason}
end.
-file("/Users/giacomocavalieri/Documents/progetti/bvalue/src/beencode.gleam", 349).
-spec decode_dict(
bitstring(),
gleam@dict:dict(bitstring(), b_value()),
integer()
) -> {ok, {b_value(), bitstring(), integer()}} | {error, b_decode_error()}.
decode_dict(String, Acc, Byte_index) ->
case String of
<<"e"/utf8, Rest/bitstring>> ->
{ok, {{b_dict, Acc}, Rest, Byte_index + 1}};
_ ->
case decode_value(String, Byte_index) of
{error, Error} ->
{error, Error};
{ok, {{b_string, Key}, Rest@1, Byte_index@1}} ->
case decode_value(Rest@1, Byte_index@1) of
{error, Error@1} ->
{error, Error@1};
{ok, {Value, Rest@2, Byte_index@2}} ->
decode_dict(
Rest@2,
gleam@dict:insert(Acc, Key, Value),
Byte_index@2
)
end;
{ok, {_, _, Byte_index@3}} ->
{error, {invalid_dict_key, Byte_index@3}}
end
end.