Current section

Files

Jump to
distribute src distribute@codec@variant.erl
Raw

src/distribute@codec@variant.erl

-module(distribute@codec@variant).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/distribute/codec/variant.gleam").
-export([new/0, add/6, unit/5, build/1]).
-export_type([variant_strategy/1, variant_builder/1]).
-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 variant_strategy(HSZ) :: {variant_strategy,
integer(),
binary(),
fun((HSZ) -> {ok, bitstring()} | {error, nil}),
fun((bitstring()) -> {ok, {HSZ, bitstring()}} |
{error, distribute@codec:decode_error()})}.
-opaque variant_builder(HTA) :: {variant_builder, list(variant_strategy(HTA))}.
-file("src/distribute/codec/variant.gleam", 30).
?DOC(" Create a new builder for an ADT codec.\n").
-spec new() -> variant_builder(any()).
new() ->
{variant_builder, []}.
-file("src/distribute/codec/variant.gleam", 38).
?DOC(
" Add a variant with a payload to the codec.\n"
" id: unique identifier (0-255). name: for error messages.\n"
" inner: codec for the payload. wrap: ADT constructor.\n"
" unwrap: extract payload or return Error(Nil) if wrong variant.\n"
).
-spec add(
variant_builder(HTD),
integer(),
binary(),
distribute@codec:codec(HTF),
fun((HTF) -> HTD),
fun((HTD) -> {ok, HTF} | {error, nil})
) -> variant_builder(HTD).
add(Builder, Id, Name, Inner, Wrap, Unwrap) ->
Strategy = {variant_strategy, Id, Name, fun(Value) -> case Unwrap(Value) of
{ok, Payload} ->
Bits@1 = case (erlang:element(2, Inner))(Payload) of
{ok, Bits} -> Bits;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"distribute/codec/variant"/utf8>>,
function => <<"add"/utf8>>,
line => 53,
value => _assert_fail,
start => 1454,
'end' => 1498,
pattern_start => 1465,
pattern_end => 1473})
end,
{ok, gleam@bit_array:append(<<Id:8>>, Bits@1)};
{error, _} ->
{error, nil}
end end, fun(Data) -> case Data of
<<Tag:8, Rest/bitstring>> when Tag =:= Id ->
case (erlang:element(4, Inner))(Rest) of
{ok, {Val, Remaining}} ->
{ok, {Wrap(Val), Remaining}};
{error, E} ->
{error, E}
end;
_ ->
{error, {tag_mismatch, Name, <<"unknown"/utf8>>}}
end end},
{variant_builder, [Strategy | erlang:element(2, Builder)]}.
-file("src/distribute/codec/variant.gleam", 76).
?DOC(" Add a variant without a payload (unit/constant).\n").
-spec unit(
variant_builder(HTK),
integer(),
binary(),
HTK,
fun((HTK) -> boolean())
) -> variant_builder(HTK).
unit(Builder, Id, Name, Value, Match) ->
Strategy = {variant_strategy, Id, Name, fun(Val) -> case Match(Val) of
true ->
{ok, <<Id:8>>};
false ->
{error, nil}
end end, fun(Data) -> case Data of
<<Tag:8, Rest/bitstring>> when Tag =:= Id ->
{ok, {Value, Rest}};
_ ->
{error, {tag_mismatch, Name, <<"unknown"/utf8>>}}
end end},
{variant_builder, [Strategy | erlang:element(2, Builder)]}.
-file("src/distribute/codec/variant.gleam", 160).
-spec find_encoder(list(variant_strategy(HTQ)), HTQ) -> {ok, bitstring()} |
{error, nil}.
find_encoder(Strategies, Value) ->
case Strategies of
[] ->
{error, nil};
[S | Rest] ->
case (erlang:element(4, S))(Value) of
{ok, Bits} ->
{ok, Bits};
{error, _} ->
find_encoder(Rest, Value)
end
end.
-file("src/distribute/codec/variant.gleam", 174).
-spec validate_strategies(list(variant_strategy(any()))) -> nil.
validate_strategies(Strategies) ->
Ids = gleam@list:map(Strategies, fun(S) -> erlang:element(2, S) end),
case begin
_pipe = gleam@list:unique(Ids),
erlang:length(_pipe)
end
=:= erlang:length(Ids) of
true ->
nil;
false ->
erlang:error(#{gleam_error => panic,
message => <<"Variant codec: Duplicate IDs detected in builder"/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"distribute/codec/variant"/utf8>>,
function => <<"validate_strategies"/utf8>>,
line => 178})
end.
-file("src/distribute/codec/variant.gleam", 110).
?DOC(
" Finalize the builder and return a Codec(a).\n"
" Panics if duplicate IDs are found.\n"
).
-spec build(variant_builder(HTN)) -> distribute@codec:codec(HTN).
build(Builder) ->
Strategies = lists:reverse(erlang:element(2, Builder)),
validate_strategies(Strategies),
Encoder = fun(Value) -> case find_encoder(Strategies, Value) of
{ok, Bits} ->
{ok, Bits};
{error, _} ->
{error,
{encode_failed,
<<"No variant matched for encoding ADT value"/utf8>>}}
end end,
Decoder_dict = gleam@list:fold(
Strategies,
maps:new(),
fun(Acc, S) -> gleam@dict:insert(Acc, erlang:element(2, S), S) end
),
Sized_decoder = fun(Data) -> case Data of
<<Tag:8, _/bitstring>> ->
case gleam_stdlib:map_get(Decoder_dict, Tag) of
{ok, Strategy} ->
(erlang:element(5, Strategy))(Data);
{error, _} ->
{error,
{tag_mismatch,
<<"one of "/utf8,
(begin
_pipe = gleam@list:map(
Strategies,
fun(S@1) ->
erlang:element(3, S@1)
end
),
_pipe@1 = gleam@list:unique(_pipe),
gleam@list:fold(
_pipe@1,
<<""/utf8>>,
fun(Acc@1, N) -> case Acc@1 of
<<""/utf8>> ->
N;
_ ->
<<<<Acc@1/binary,
", "/utf8>>/binary,
N/binary>>
end end
)
end)/binary>>,
erlang:integer_to_binary(Tag)}}
end;
_ ->
{error, {insufficient_data, <<"missing variant tag"/utf8>>}}
end end,
{codec, Encoder, distribute@codec:to_decoder(Sized_decoder), Sized_decoder}.