Skip to main content

src/packkit@lzw.erl

-module(packkit@lzw).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/lzw.gleam").
-export([codec/0, encode_with_options/3, decode_with_limits/2, decode/1, encode/1]).
-export_type([encode_state/0, decode_state/0, writer/0, reader/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.

?MODULEDOC(
    " Unix `compress(1)` LZW codec — `.Z` stream encoder and decoder.\n"
    "\n"
    " The stream begins with the two-byte magic `1F 9D` followed by a\n"
    " flag byte whose low five bits hold the maximum code width\n"
    " (9..16) and whose top bit toggles block-compress mode.  Codes\n"
    " are packed LSB-first across bytes, the dictionary starts with\n"
    " the 256 byte literals (plus a clear-table marker when block mode\n"
    " is on), and each new entry maps the previous code plus the\n"
    " current character to the next free code.  When the dictionary\n"
    " fills the current width, the encoder pads to the next\n"
    " `width * 8` bit boundary before promoting to the next width or\n"
    " emitting a clear-table code.\n"
).

-type encode_state() :: {encode_state,
        gleam@dict:dict(integer(), integer()),
        integer(),
        integer(),
        integer(),
        integer(),
        integer(),
        integer(),
        boolean(),
        writer(),
        integer()}.

-type decode_state() :: {decode_state,
        list(integer()),
        integer(),
        gleam@dict:dict(integer(), {integer(), integer()}),
        integer(),
        integer(),
        integer(),
        integer(),
        integer(),
        boolean(),
        integer(),
        boolean(),
        integer()}.

-type writer() :: {writer, list(integer()), integer(), integer(), integer()}.

-type reader() :: {reader,
        bitstring(),
        integer(),
        integer(),
        integer(),
        boolean()}.

-file("src/packkit/lzw.gleam", 36).
?DOC(" LZW codec smart constructor (Unix `.Z` family).\n").
-spec codec() -> packkit@codec:codec().
codec() ->
    packkit@codec:lzw().

-file("src/packkit/lzw.gleam", 200).
-spec dictionary_key(integer(), integer()) -> integer().
dictionary_key(Prefix, Byte) ->
    erlang:'bor'(erlang:'bsl'(Prefix, 8), Byte).

-file("src/packkit/lzw.gleam", 222).
-spec max_max_code(integer()) -> integer().
max_max_code(Max_bits) ->
    erlang:'bsl'(1, Max_bits).

-file("src/packkit/lzw.gleam", 204).
-spec max_code_for(integer(), integer()) -> integer().
max_code_for(N_bits, Max_bits) ->
    case N_bits >= Max_bits of
        true ->
            max_max_code(Max_bits);

        false ->
            erlang:'bsl'(1, N_bits)
    end.

-file("src/packkit/lzw.gleam", 425).
?DOC(
    " The decoder must promote one iteration \"earlier\" than the encoder\n"
    " because of the classical LZW insertion off-by-one: encoder inserts\n"
    " the `(prefix, byte)` pair at the iter that writes the OLD prefix's\n"
    " code, while the decoder inserts `(prev_code, first_of_current_code)`\n"
    " at the iter that reads the current code — same final entries, but\n"
    " the decoder's `free_ent` lags one bump behind the encoder's view\n"
    " of the stream.  We check `free_ent >= max_code` so the decoder\n"
    " promotes between codes 254 and 255 (when the encoder pads), instead\n"
    " of between codes 255 and 256 (when the decoder's own free_ent would\n"
    " naturally exceed max_code).  Without this, a 256-unique-byte stream\n"
    " reads the encoder's 9-bit pad as a phantom literal code 0.\n"
).
-spec promote_decoder_width(decode_state(), reader()) -> decode_state().
promote_decoder_width(State, _) ->
    case (erlang:element(5, State) >= erlang:element(7, State)) andalso (erlang:element(
        6,
        State
    )
    < erlang:element(11, State)) of
        true ->
            {decode_state,
                erlang:element(2, State),
                erlang:element(3, State),
                erlang:element(4, State),
                erlang:element(5, State),
                erlang:element(6, State),
                erlang:element(7, State),
                -1,
                erlang:element(9, State),
                erlang:element(10, State),
                erlang:element(11, State),
                erlang:element(12, State),
                erlang:element(13, State)};

        false ->
            State
    end.

-file("src/packkit/lzw.gleam", 462).
-spec check_output_limit(integer(), packkit@limit:limits()) -> {ok, nil} |
    {error, packkit@error:codec_error()}.
check_output_limit(Size, Limits) ->
    case Size > packkit@limit:max_output_bytes(Limits) of
        true ->
            {error, {codec_limit_exceeded, <<"max_output_bytes"/utf8>>, Size}};

        false ->
            {ok, nil}
    end.

-file("src/packkit/lzw.gleam", 480).
-spec expand_loop(
    integer(),
    gleam@dict:dict(integer(), {integer(), integer()}),
    list(integer()),
    integer()
) -> {ok, {list(integer()), integer()}} | {error, packkit@error:codec_error()}.
expand_loop(Code, Table, Acc, Count) ->
    case Code < 256 of
        true ->
            {ok, {[Code | Acc], Count + 1}};

        false ->
            case gleam_stdlib:map_get(Table, Code) of
                {ok, {Prev, Char}} ->
                    expand_loop(Prev, Table, [Char | Acc], Count + 1);

                {error, _} ->
                    {error,
                        {codec_invalid_data, <<"lzw code lookup failed"/utf8>>}}
            end
    end.

-file("src/packkit/lzw.gleam", 473).
-spec expand_code(integer(), gleam@dict:dict(integer(), {integer(), integer()})) -> {ok,
        {list(integer()), integer()}} |
    {error, packkit@error:codec_error()}.
expand_code(Code, Table) ->
    expand_loop(Code, Table, [], 0).

-file("src/packkit/lzw.gleam", 497).
-spec first_char(integer(), gleam@dict:dict(integer(), {integer(), integer()})) -> {ok,
        integer()} |
    {error, packkit@error:codec_error()}.
first_char(Code, Table) ->
    case Code < 256 of
        true ->
            {ok, Code};

        false ->
            case gleam_stdlib:map_get(Table, Code) of
                {ok, {Prev, _}} ->
                    first_char(Prev, Table);

                {error, _} ->
                    {error,
                        {codec_invalid_data,
                            <<"lzw first-char lookup failed"/utf8>>}}
            end
    end.

-file("src/packkit/lzw.gleam", 512).
-spec first_char_of_chars(list(integer())) -> {ok, integer()} |
    {error, packkit@error:codec_error()}.
first_char_of_chars(Chars) ->
    case Chars of
        [Head | _] ->
            {ok, Head};

        [] ->
            {error,
                {codec_invalid_data, <<"lzw expansion produced no bytes"/utf8>>}}
    end.

-file("src/packkit/lzw.gleam", 520).
-spec prepend_chars(list(integer()), list(integer())) -> list(integer()).
prepend_chars(Chars, Acc) ->
    case Chars of
        [] ->
            Acc;

        [Head | Rest] ->
            prepend_chars(Rest, [Head | Acc])
    end.

-file("src/packkit/lzw.gleam", 432).
-spec emit_code(
    integer(),
    list(integer()),
    integer(),
    gleam@dict:dict(integer(), {integer(), integer()}),
    integer(),
    boolean(),
    integer(),
    packkit@limit:limits()
) -> {ok,
        {list(integer()),
            integer(),
            gleam@dict:dict(integer(), {integer(), integer()}),
            integer()}} |
    {error, packkit@error:codec_error()}.
emit_code(Code, Out_rev, Out_len, Table, Prev_code, Has_prev, Free_ent, Limits) ->
    case (Code =:= Free_ent) andalso Has_prev of
        true ->
            gleam@result:'try'(
                first_char(Prev_code, Table),
                fun(First) ->
                    Extended_table = gleam@dict:insert(
                        Table,
                        Code,
                        {Prev_code, First}
                    ),
                    gleam@result:'try'(
                        expand_code(Code, Extended_table),
                        fun(_use0) ->
                            {Chars, Count} = _use0,
                            gleam@result:'try'(
                                check_output_limit(Out_len + Count, Limits),
                                fun(_) ->
                                    {ok,
                                        {prepend_chars(Chars, Out_rev),
                                            Out_len + Count,
                                            Table,
                                            First}}
                                end
                            )
                        end
                    )
                end
            );

        false ->
            gleam@result:'try'(
                expand_code(Code, Table),
                fun(_use0@1) ->
                    {Chars@1, Count@1} = _use0@1,
                    gleam@result:'try'(
                        first_char_of_chars(Chars@1),
                        fun(First@1) ->
                            gleam@result:'try'(
                                check_output_limit(Out_len + Count@1, Limits),
                                fun(_) ->
                                    {ok,
                                        {prepend_chars(Chars@1, Out_rev),
                                            Out_len + Count@1,
                                            Table,
                                            First@1}}
                                end
                            )
                        end
                    )
                end
            )
    end.

-file("src/packkit/lzw.gleam", 527).
-spec reverse_to_bit_array(list(integer()), bitstring()) -> bitstring().
reverse_to_bit_array(Values, Acc) ->
    case Values of
        [] ->
            Acc;

        [Head | Rest] ->
            reverse_to_bit_array(Rest, <<Head, Acc/bitstring>>)
    end.

-file("src/packkit/lzw.gleam", 540).
-spec new_writer() -> writer().
new_writer() ->
    {writer, [], 0, 0, 0}.

-file("src/packkit/lzw.gleam", 568).
-spec mask_for(integer()) -> integer().
mask_for(Count) ->
    erlang:'bsl'(1, Count) - 1.

-file("src/packkit/lzw.gleam", 572).
-spec flush_full_bytes(writer()) -> writer().
flush_full_bytes(Writer) ->
    case erlang:element(4, Writer) >= 8 of
        false ->
            Writer;

        true ->
            flush_full_bytes(
                {writer,
                    [erlang:'band'(erlang:element(3, Writer), 16#FF) |
                        erlang:element(2, Writer)],
                    erlang:'bsr'(erlang:element(3, Writer), 8),
                    erlang:element(4, Writer) - 8,
                    erlang:element(5, Writer)}
            )
    end.

-file("src/packkit/lzw.gleam", 548).
-spec write_bits(writer(), integer(), integer()) -> writer().
write_bits(Writer, Value, Count) ->
    case Count of
        0 ->
            Writer;

        _ ->
            Masked = erlang:'band'(Value, mask_for(Count)),
            Buffer = erlang:'bor'(
                erlang:element(3, Writer),
                erlang:'bsl'(Masked, erlang:element(4, Writer))
            ),
            flush_full_bytes(
                {writer,
                    erlang:element(2, Writer),
                    Buffer,
                    erlang:element(4, Writer) + Count,
                    erlang:element(5, Writer) + Count}
            )
    end.

-file("src/packkit/lzw.gleam", 275).
-spec pad_zero_bits(writer(), integer()) -> writer().
pad_zero_bits(Writer, Count) ->
    case Count of
        0 ->
            Writer;

        N when N >= 32 ->
            pad_zero_bits(write_bits(Writer, 0, 32), N - 32);

        N@1 ->
            write_bits(Writer, 0, N@1)
    end.

-file("src/packkit/lzw.gleam", 266).
-spec pad_writer_to_block(writer(), integer()) -> writer().
pad_writer_to_block(Writer, N_bits) ->
    Group_bits = 8 * N_bits,
    Leftover = case Group_bits of
        0 -> 0;
        Gleam@denominator -> erlang:element(5, Writer) rem Gleam@denominator
    end,
    case Leftover of
        0 ->
            Writer;

        _ ->
            pad_zero_bits(Writer, Group_bits - Leftover)
    end.

-file("src/packkit/lzw.gleam", 226).
-spec promote_width(encode_state()) -> encode_state().
promote_width(State) ->
    case (erlang:element(4, State) > erlang:element(6, State)) andalso (erlang:element(
        5,
        State
    )
    < erlang:element(8, State)) of
        true ->
            Writer = pad_writer_to_block(
                erlang:element(10, State),
                erlang:element(5, State)
            ),
            New_n_bits = erlang:element(5, State) + 1,
            {encode_state,
                erlang:element(2, State),
                erlang:element(3, State),
                erlang:element(4, State),
                New_n_bits,
                max_code_for(New_n_bits, erlang:element(8, State)),
                0,
                erlang:element(8, State),
                erlang:element(9, State),
                Writer,
                erlang:element(11, State)};

        false ->
            State
    end.

-file("src/packkit/lzw.gleam", 544).
-spec write_code(writer(), integer(), integer()) -> writer().
write_code(Writer, Code, N_bits) ->
    write_bits(Writer, Code, N_bits).

-file("src/packkit/lzw.gleam", 262).
-spec write_code_with_padding(encode_state(), integer()) -> writer().
write_code_with_padding(State, Code) ->
    write_code(erlang:element(10, State), Code, erlang:element(5, State)).

-file("src/packkit/lzw.gleam", 599).
-spec list_reverse_to_bit_array(list(integer()), bitstring()) -> bitstring().
list_reverse_to_bit_array(Values, Acc) ->
    case Values of
        [] ->
            Acc;

        [Head | Rest] ->
            list_reverse_to_bit_array(Rest, <<Head, Acc/bitstring>>)
    end.

-file("src/packkit/lzw.gleam", 585).
-spec flush_writer(writer()) -> bitstring().
flush_writer(Writer) ->
    Writer@1 = case erlang:element(4, Writer) of
        0 ->
            Writer;

        _ ->
            {writer,
                [erlang:'band'(erlang:element(3, Writer), 16#FF) |
                    erlang:element(2, Writer)],
                0,
                0,
                erlang:element(5, Writer)}
    end,
    list_reverse_to_bit_array(erlang:element(2, Writer@1), <<>>).

-file("src/packkit/lzw.gleam", 618).
-spec new_reader(bitstring()) -> reader().
new_reader(Source) ->
    {reader, Source, 0, 0, 0, false}.

-file("src/packkit/lzw.gleam", 622).
-spec refill(reader(), integer()) -> reader().
refill(Reader, Needed) ->
    case (erlang:element(4, Reader) >= Needed) orelse erlang:element(6, Reader) of
        true ->
            Reader;

        false ->
            case erlang:element(2, Reader) of
                <<B, Rest/binary>> ->
                    refill(
                        {reader,
                            Rest,
                            erlang:'bor'(
                                erlang:element(3, Reader),
                                erlang:'bsl'(B, erlang:element(4, Reader))
                            ),
                            erlang:element(4, Reader) + 8,
                            erlang:element(5, Reader),
                            false},
                        Needed
                    );

                _ ->
                    {reader,
                        <<>>,
                        erlang:element(3, Reader),
                        erlang:element(4, Reader),
                        erlang:element(5, Reader),
                        true}
            end
    end.

-file("src/packkit/lzw.gleam", 653).
-spec read_code(reader(), integer()) -> {ok, {integer(), reader()}} |
    {error, nil}.
read_code(Reader, N_bits) ->
    Reader@1 = refill(Reader, N_bits),
    case erlang:element(4, Reader@1) >= N_bits of
        false ->
            {error, nil};

        true ->
            Mask = mask_for(N_bits),
            Value = erlang:'band'(erlang:element(3, Reader@1), Mask),
            {ok,
                {Value,
                    {reader,
                        erlang:element(2, Reader@1),
                        erlang:'bsr'(erlang:element(3, Reader@1), N_bits),
                        erlang:element(4, Reader@1) - N_bits,
                        erlang:element(5, Reader@1) + N_bits,
                        erlang:element(6, Reader@1)}}}
    end.

-file("src/packkit/lzw.gleam", 683).
-spec drop_bits(reader(), integer()) -> reader().
drop_bits(Reader, Count) ->
    case Count of
        0 ->
            Reader;

        _ ->
            Reader@1 = refill(Reader, 1),
            case erlang:element(4, Reader@1) >= 1 of
                false ->
                    Reader@1;

                true ->
                    drop_bits(
                        {reader,
                            erlang:element(2, Reader@1),
                            erlang:'bsr'(erlang:element(3, Reader@1), 1),
                            erlang:element(4, Reader@1) - 1,
                            erlang:element(5, Reader@1) + 1,
                            erlang:element(6, Reader@1)},
                        Count - 1
                    )
            end
    end.

-file("src/packkit/lzw.gleam", 674).
-spec skip_to_block_boundary(reader(), integer()) -> reader().
skip_to_block_boundary(Reader, N_bits) ->
    Group_bits = 8 * N_bits,
    Leftover = case Group_bits of
        0 -> 0;
        Gleam@denominator -> erlang:element(5, Reader) rem Gleam@denominator
    end,
    case Leftover of
        0 ->
            Reader;

        _ ->
            drop_bits(Reader, Group_bits - Leftover)
    end.

-file("src/packkit/lzw.gleam", 106).
-spec build_header(integer(), boolean()) -> bitstring().
build_header(Max_bits, Block_mode) ->
    Flag = case Block_mode of
        true ->
            erlang:'bor'(Max_bits, 16#80);

        false ->
            Max_bits
    end,
    <<16#1F, 16#9D, Flag>>.

-file("src/packkit/lzw.gleam", 243).
-spec maybe_emit_clear(encode_state()) -> encode_state().
maybe_emit_clear(State) ->
    case erlang:element(9, State) andalso (erlang:element(4, State) > max_max_code(
        erlang:element(8, State)
    )) of
        true ->
            Writer = write_code(
                erlang:element(10, State),
                256,
                erlang:element(5, State)
            ),
            Writer@1 = pad_writer_to_block(Writer, erlang:element(5, State)),
            {encode_state,
                maps:new(),
                erlang:element(3, State),
                erlang:element(11, State),
                9,
                max_code_for(9, erlang:element(8, State)),
                0,
                erlang:element(8, State),
                erlang:element(9, State),
                Writer@1,
                erlang:element(11, State)};

        false ->
            State
    end.

-file("src/packkit/lzw.gleam", 160).
-spec encode_loop(bitstring(), encode_state()) -> encode_state().
encode_loop(Input, State) ->
    case Input of
        <<Byte, Rest/binary>> ->
            Key = dictionary_key(erlang:element(3, State), Byte),
            case gleam_stdlib:map_get(erlang:element(2, State), Key) of
                {ok, Code} ->
                    encode_loop(
                        Rest,
                        {encode_state,
                            erlang:element(2, State),
                            Code,
                            erlang:element(4, State),
                            erlang:element(5, State),
                            erlang:element(6, State),
                            erlang:element(7, State),
                            erlang:element(8, State),
                            erlang:element(9, State),
                            erlang:element(10, State),
                            erlang:element(11, State)}
                    );

                {error, _} ->
                    Writer = write_code_with_padding(
                        State,
                        erlang:element(3, State)
                    ),
                    Next_state = case erlang:element(4, State) =< max_max_code(
                        erlang:element(8, State)
                    ) of
                        true ->
                            Table = gleam@dict:insert(
                                erlang:element(2, State),
                                Key,
                                erlang:element(4, State)
                            ),
                            promote_width(
                                {encode_state,
                                    Table,
                                    Byte,
                                    erlang:element(4, State) + 1,
                                    erlang:element(5, State),
                                    erlang:element(6, State),
                                    erlang:element(7, State) + 1,
                                    erlang:element(8, State),
                                    erlang:element(9, State),
                                    Writer,
                                    erlang:element(11, State)}
                            );

                        false ->
                            maybe_emit_clear(
                                {encode_state,
                                    erlang:element(2, State),
                                    Byte,
                                    erlang:element(4, State),
                                    erlang:element(5, State),
                                    erlang:element(6, State),
                                    erlang:element(7, State) + 1,
                                    erlang:element(8, State),
                                    erlang:element(9, State),
                                    Writer,
                                    erlang:element(11, State)}
                            )
                    end,
                    encode_loop(Rest, Next_state)
            end;

        _ ->
            State
    end.

-file("src/packkit/lzw.gleam", 131).
-spec encode_payload(bitstring(), integer(), boolean(), integer()) -> bitstring().
encode_payload(Bytes, Max_bits, Block_mode, First_free) ->
    case Bytes of
        <<First, Rest/binary>> ->
            State = {encode_state,
                maps:new(),
                First,
                First_free,
                9,
                max_code_for(9, Max_bits),
                0,
                Max_bits,
                Block_mode,
                new_writer(),
                First_free},
            State@1 = encode_loop(Rest, State),
            Writer = write_code_with_padding(
                State@1,
                erlang:element(3, State@1)
            ),
            flush_writer(Writer);

        _ ->
            flush_writer(new_writer())
    end.

-file("src/packkit/lzw.gleam", 51).
?DOC(" Encode `bytes` as a `.Z` stream with explicit options.\n").
-spec encode_with_options(bitstring(), integer(), boolean()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
encode_with_options(Bytes, Max_bits, Block_mode) ->
    gleam@bool:guard(
        (Max_bits < 9) orelse (Max_bits > 16),
        {error,
            {codec_invalid_data, <<"lzw max_bits out of range (9..16)"/utf8>>}},
        fun() ->
            Header = build_header(Max_bits, Block_mode),
            First_free = case Block_mode of
                true ->
                    257;

                false ->
                    256
            end,
            Payload = encode_payload(Bytes, Max_bits, Block_mode, First_free),
            {ok, gleam_stdlib:bit_array_concat([Header, Payload])}
        end
    ).

-file("src/packkit/lzw.gleam", 333).
-spec decode_loop(reader(), decode_state(), packkit@limit:limits()) -> {ok,
        {list(integer()), integer()}} |
    {error, packkit@error:codec_error()}.
decode_loop(Reader, State, Limits) ->
    case read_code(Reader, erlang:element(6, State)) of
        {error, _} ->
            {ok, {erlang:element(2, State), erlang:element(3, State)}};

        {ok, {Code, Reader@1}} ->
            case erlang:element(12, State) andalso (Code =:= 256) of
                true ->
                    Reader@2 = skip_to_block_boundary(
                        Reader@1,
                        erlang:element(6, State)
                    ),
                    New_state = {decode_state,
                        erlang:element(2, State),
                        erlang:element(3, State),
                        maps:new(),
                        erlang:element(13, State),
                        9,
                        max_code_for(9, erlang:element(11, State)),
                        0,
                        -1,
                        false,
                        erlang:element(11, State),
                        erlang:element(12, State),
                        erlang:element(13, State)},
                    decode_loop(Reader@2, New_state, Limits);

                false ->
                    gleam@result:'try'(
                        emit_code(
                            Code,
                            erlang:element(2, State),
                            erlang:element(3, State),
                            erlang:element(4, State),
                            erlang:element(9, State),
                            erlang:element(10, State),
                            erlang:element(5, State),
                            Limits
                        ),
                        fun(_use0) ->
                            {Out_rev, Out_len, Table, Prefix_first} = _use0,
                            {New_table, New_free_ent} = case erlang:element(
                                10,
                                State
                            ) of
                                true ->
                                    {gleam@dict:insert(
                                            Table,
                                            erlang:element(5, State),
                                            {erlang:element(9, State),
                                                Prefix_first}
                                        ),
                                        erlang:element(5, State) + 1};

                                false ->
                                    {Table, erlang:element(5, State)}
                            end,
                            New_state@1 = {decode_state,
                                Out_rev,
                                Out_len,
                                New_table,
                                New_free_ent,
                                erlang:element(6, State),
                                erlang:element(7, State),
                                erlang:element(8, State),
                                Code,
                                true,
                                erlang:element(11, State),
                                erlang:element(12, State),
                                erlang:element(13, State)},
                            New_state@2 = promote_decoder_width(
                                New_state@1,
                                Reader@1
                            ),
                            {New_state@3, Reader@4} = case erlang:element(
                                8,
                                New_state@2
                            ) of
                                -1 ->
                                    Reader@3 = skip_to_block_boundary(
                                        Reader@1,
                                        erlang:element(6, State)
                                    ),
                                    {{decode_state,
                                            erlang:element(2, New_state@2),
                                            erlang:element(3, New_state@2),
                                            erlang:element(4, New_state@2),
                                            erlang:element(5, New_state@2),
                                            erlang:element(6, New_state@2) + 1,
                                            max_code_for(
                                                erlang:element(6, New_state@2) + 1,
                                                erlang:element(11, New_state@2)
                                            ),
                                            0,
                                            erlang:element(9, New_state@2),
                                            erlang:element(10, New_state@2),
                                            erlang:element(11, New_state@2),
                                            erlang:element(12, New_state@2),
                                            erlang:element(13, New_state@2)},
                                        Reader@3};

                                _ ->
                                    {New_state@2, Reader@1}
                            end,
                            decode_loop(Reader@4, New_state@3, Limits)
                        end
                    )
            end
    end.

-file("src/packkit/lzw.gleam", 302).
-spec decode_payload(bitstring(), integer(), boolean(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_payload(Bytes, Max_bits, Block_mode, Limits) ->
    First_free = case Block_mode of
        true ->
            257;

        false ->
            256
    end,
    Reader = new_reader(Bytes),
    State = {decode_state,
        [],
        0,
        maps:new(),
        First_free,
        9,
        max_code_for(9, Max_bits),
        0,
        -1,
        false,
        Max_bits,
        Block_mode,
        First_free},
    gleam@result:'try'(
        decode_loop(Reader, State, Limits),
        fun(_use0) ->
            {Out_rev, Out_len} = _use0,
            gleam@result:'try'(
                check_output_limit(Out_len, Limits),
                fun(_) -> {ok, reverse_to_bit_array(Out_rev, <<>>)} end
            )
        end
    ).

-file("src/packkit/lzw.gleam", 77).
?DOC(" Decode a `.Z` stream using explicit `Limits`.\n").
-spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok,
        bitstring()} |
    {error, packkit@error:codec_error()}.
decode_with_limits(Bytes, Limits) ->
    gleam@bool:guard(
        erlang:byte_size(Bytes) > packkit@limit:max_input_bytes(Limits),
        {error,
            {codec_limit_exceeded,
                <<"max_input_bytes"/utf8>>,
                erlang:byte_size(Bytes)}},
        fun() -> case Bytes of
                <<M1, M2, Flag, Rest/binary>> when (M1 =:= 16#1F) andalso (M2 =:= 16#9D) ->
                    Max_bits = erlang:'band'(Flag, 16#1F),
                    Block_mode = erlang:'band'(Flag, 16#80) /= 0,
                    gleam@bool:guard(
                        (Max_bits < 9) orelse (Max_bits > 16),
                        {error,
                            {codec_invalid_data,
                                <<"lzw header advertises invalid max_bits"/utf8>>}},
                        fun() ->
                            decode_payload(Rest, Max_bits, Block_mode, Limits)
                        end
                    );

                _ ->
                    {error,
                        {codec_invalid_data,
                            <<"lzw stream missing 1F 9D magic"/utf8>>}}
            end end
    ).

-file("src/packkit/lzw.gleam", 72).
?DOC(" Decode a `.Z` stream using the shared default `Limits`.\n").
-spec decode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
decode(Bytes) ->
    decode_with_limits(Bytes, packkit@limit:default()).

-file("src/packkit/lzw.gleam", 42).
?DOC(
    " Encode `bytes` as a `.Z` stream using the default 16-bit\n"
    " block-mode encoder.\n"
).
-spec encode(bitstring()) -> {ok, bitstring()} |
    {error, packkit@error:codec_error()}.
encode(Bytes) ->
    encode_with_options(Bytes, 16, true).