Current section
Files
Jump to
Current section
Files
src/bitty@bytes.erl
-module(bitty@bytes).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/bitty/bytes.gleam").
-export([take/1, byte/0, skip/1, rest/0, peek/1, tag/1, byte_if/1, take_while/1, take_while1/1, take_until/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.
?MODULEDOC(
" Byte-level parsers for reading, skipping, peeking, and matching raw\n"
" bytes. These parsers work at any bit position — when unaligned, bytes\n"
" are extracted bit-by-bit. When byte-aligned, zero-copy slicing is used.\n"
" After bit-level parsing with `bitty/bits`, call `bits.align()` before\n"
" resuming byte-aligned operations.\n"
).
-file("src/bitty/bytes.gleam", 24).
?DOC(
" Parse exactly `count` bytes as a `BitArray`.\n"
" When byte-aligned, returns a zero-copy slice.\n"
).
-spec take(integer()) -> bitty:parser(bitstring()).
take(Count) ->
bitty:make_parser(fun(_capture) -> bitty:read_n_bytes(_capture, Count) end).
-file("src/bitty/bytes.gleam", 18).
?DOC(
" Parse a single byte and return it as a `BitArray`.\n"
"\n"
" ```gleam\n"
" let assert Ok(b) = bitty.run(bytes.byte(), on: <<0xFF>>)\n"
" assert b == <<0xFF>>\n"
" ```\n"
).
-spec byte() -> bitty:parser(bitstring()).
byte() ->
take(1).
-file("src/bitty/bytes.gleam", 29).
?DOC(" Advance the parser position by `count` bytes, discarding the data.\n").
-spec skip(integer()) -> bitty:parser(nil).
skip(Count) ->
bitty:make_parser(
fun(State) ->
gleam@bool:guard(
Count < 0,
bitty:stop_expected(State, <<"non-negative skip count"/utf8>>),
fun() ->
Total_bits = Count * 8,
{New_byte, New_bit} = bitty:advance_bits(
erlang:element(3, State),
erlang:element(4, State),
Total_bits
),
Input_size = erlang:byte_size(erlang:element(2, State)),
At_valid_position = case New_bit of
0 ->
New_byte =< Input_size;
_ ->
New_byte < Input_size
end,
gleam@bool:guard(
not At_valid_position,
bitty:stop_expected(
State,
<<<<"at least "/utf8,
(erlang:integer_to_binary(Count))/binary>>/binary,
" bytes"/utf8>>
),
fun() ->
{continue,
nil,
{state,
erlang:element(2, State),
New_byte,
New_bit,
erlang:element(5, State)},
Count > 0}
end
)
end
)
end
).
-file("src/bitty/bytes.gleam", 93).
-spec rest_read_bytes(bitty:state(), integer()) -> bitty:step(bitstring()).
rest_read_bytes(State, Count) ->
case Count > 0 of
true ->
bitty:read_n_bytes(State, Count);
false ->
{continue, <<>>, State, false}
end.
-file("src/bitty/bytes.gleam", 100).
-spec rest_read_trailing(bitty:state(), integer()) -> bitty:step(bitstring()).
rest_read_trailing(State, Count) ->
case Count > 0 of
true ->
case bitty:read_uint(State, Count, 0, true) of
{continue, Val, End_state, _} ->
{continue,
<<Val:(lists:max([(Count), 0]))>>,
End_state,
true};
{stop, E, C, Cm} ->
{stop, E, C, Cm}
end;
false ->
{continue, <<>>, State, false}
end.
-file("src/bitty/bytes.gleam", 77).
-spec rest_read(bitty:state(), integer(), integer()) -> bitty:step(bitstring()).
rest_read(State, Remaining_bytes, Trailing_bits) ->
case rest_read_bytes(State, Remaining_bytes) of
{continue, Bytes, Mid_state, _} ->
case rest_read_trailing(Mid_state, Trailing_bits) of
{continue, Trail, End_state, _} ->
{continue,
<<Bytes/bitstring, Trail/bitstring>>,
End_state,
true};
{stop, E, C, Cm} ->
{stop, E, C, Cm}
end;
{stop, E@1, C@1, Cm@1} ->
{stop, E@1, C@1, Cm@1}
end.
-file("src/bitty/bytes.gleam", 60).
?DOC(
" Consume all remaining input as a `BitArray`.\n"
" Works at any bit position — the result may be non-byte-aligned.\n"
).
-spec rest() -> bitty:parser(bitstring()).
rest() ->
bitty:make_parser(
fun(State) ->
Total_remaining_bits = ((erlang:byte_size(erlang:element(2, State))
- erlang:element(3, State))
* 8)
- erlang:element(4, State),
gleam@bool:guard(
Total_remaining_bits =< 0,
{continue, <<>>, State, false},
fun() ->
Remaining_bytes = Total_remaining_bits div 8,
Trailing_bits = Total_remaining_bits rem 8,
rest_read(State, Remaining_bytes, Trailing_bits)
end
)
end
).
-file("src/bitty/bytes.gleam", 113).
?DOC(" Look ahead at the next `count` bytes without consuming them.\n").
-spec peek(integer()) -> bitty:parser(bitstring()).
peek(Count) ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, Count) of
{continue, Bytes, _, _} ->
{continue, Bytes, State, false};
{stop, Error, Consumed, Committed} ->
{stop, Error, Consumed, Committed}
end end).
-file("src/bitty/bytes.gleam", 125).
?DOC(
" Match an exact byte sequence at the current position and consume it.\n"
" Fails if the bytes don't match. Useful for magic numbers and fixed headers.\n"
).
-spec tag(bitstring()) -> bitty:parser(nil).
tag(Expected) ->
Len = erlang:byte_size(Expected),
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, Len) of
{continue, Actual, New_state, Consumed} when Actual =:= Expected ->
{continue, nil, New_state, Consumed};
{continue, _, _, _} ->
bitty:stop_expected(State, <<"tag"/utf8>>);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end).
-file("src/bitty/bytes.gleam", 148).
?DOC(
" Parse a single byte that satisfies the given predicate.\n"
" The predicate receives a 1-byte `BitArray`.\n"
" When byte-aligned, returns a zero-copy slice. When unaligned, the byte\n"
" is extracted individually.\n"
"\n"
" ```gleam\n"
" let parser = bytes.byte_if(fn(b) { b == <<0xFF>> })\n"
" let assert Ok(value) = bitty.run(parser, on: <<0xFF>>)\n"
" assert value == <<0xFF>>\n"
" ```\n"
).
-spec byte_if(fun((bitstring()) -> boolean())) -> bitty:parser(bitstring()).
byte_if(Predicate) ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, 1) of
{continue, Matched, New_state, _} ->
case Predicate(Matched) of
true ->
{continue, Matched, New_state, true};
false ->
bitty:stop_expected(State, <<"matching byte"/utf8>>)
end;
{stop, _, _, _} ->
bitty:stop_expected(State, <<"a byte"/utf8>>)
end end).
-file("src/bitty/bytes.gleam", 200).
-spec finish_byte_slice(bitty:state(), integer()) -> bitty:step(bitstring()).
finish_byte_slice(State, Start) ->
Len = erlang:element(3, State) - Start,
case Len of
0 ->
{continue, <<>>, State, false};
_ ->
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
Start,
Len
) of
{ok, Bytes} ->
{continue, Bytes, State, true};
_ ->
bitty:stop_expected(State, <<"valid slice"/utf8>>)
end
end.
-file("src/bitty/bytes.gleam", 180).
-spec take_while_loop(bitty:state(), integer(), fun((bitstring()) -> boolean())) -> bitty:step(bitstring()).
take_while_loop(State, Start, Predicate) ->
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
erlang:element(3, State),
1
) of
{ok, Matched} ->
case Predicate(Matched) of
true ->
take_while_loop(
{state,
erlang:element(2, State),
erlang:element(3, State) + 1,
erlang:element(4, State),
erlang:element(5, State)},
Start,
Predicate
);
false ->
finish_byte_slice(State, Start)
end;
_ ->
finish_byte_slice(State, Start)
end.
-file("src/bitty/bytes.gleam", 212).
-spec take_while_unaligned(
bitty:state(),
bitstring(),
fun((bitstring()) -> boolean())
) -> bitty:step(bitstring()).
take_while_unaligned(State, Acc, Predicate) ->
case bitty:read_n_bytes(State, 1) of
{continue, Matched, New_state, _} ->
case Predicate(Matched) of
true ->
take_while_unaligned(
New_state,
<<Acc/bitstring, Matched/bitstring>>,
Predicate
);
false ->
{continue, Acc, State, erlang:byte_size(Acc) > 0}
end;
{stop, _, _, _} ->
{continue, Acc, State, erlang:byte_size(Acc) > 0}
end.
-file("src/bitty/bytes.gleam", 171).
?DOC(
" Consume bytes while the predicate holds.\n"
" Returns `<<>>` when zero bytes match.\n"
" When byte-aligned, returns a zero-copy slice. When unaligned, bytes are\n"
" extracted individually.\n"
"\n"
" ```gleam\n"
" let parser = bytes.take_while(fn(b) { b != <<0x00>> })\n"
" let assert Ok(value) = bitty.run(parser, on: <<1, 2, 3, 0x00>>)\n"
" assert value == <<1, 2, 3>>\n"
" ```\n"
).
-spec take_while(fun((bitstring()) -> boolean())) -> bitty:parser(bitstring()).
take_while(Predicate) ->
bitty:make_parser(fun(State) -> case erlang:element(4, State) =:= 0 of
true ->
take_while_loop(State, erlang:element(3, State), Predicate);
false ->
take_while_unaligned(State, <<>>, Predicate)
end end).
-file("src/bitty/bytes.gleam", 238).
?DOC(
" Like `take_while` but requires at least one matching byte.\n"
" When byte-aligned, returns a zero-copy slice. When unaligned, bytes are\n"
" extracted individually.\n"
"\n"
" ```gleam\n"
" let parser = bytes.take_while1(fn(b) { b != <<0x00>> })\n"
" let assert Ok(value) = bitty.run(parser, on: <<1, 2, 3, 0x00>>)\n"
" assert value == <<1, 2, 3>>\n"
" ```\n"
).
-spec take_while1(fun((bitstring()) -> boolean())) -> bitty:parser(bitstring()).
take_while1(Predicate) ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, 1) of
{continue, Matched, New_state, _} ->
case Predicate(Matched) of
true ->
case erlang:element(4, State) =:= 0 of
true ->
take_while_loop(
New_state,
erlang:element(3, State),
Predicate
);
false ->
take_while_unaligned(
New_state,
Matched,
Predicate
)
end;
false ->
bitty:stop_expected(
State,
<<"at least one matching byte"/utf8>>
)
end;
{stop, _, _, _} ->
bitty:stop_expected(
State,
<<"at least one matching byte"/utf8>>
)
end end).
-file("src/bitty/bytes.gleam", 266).
?DOC(
" Consume bytes until the predicate matches, returning everything before\n"
" the matching byte. Returns `<<>>` when zero bytes match.\n"
" When byte-aligned, returns a zero-copy slice. When unaligned, bytes are\n"
" extracted individually.\n"
"\n"
" ```gleam\n"
" let parser = bytes.take_until(fn(b) { b == <<0x00>> })\n"
" let assert Ok(value) = bitty.run(parser, on: <<1, 2, 3, 0x00>>)\n"
" assert value == <<1, 2, 3>>\n"
" ```\n"
).
-spec take_until(fun((bitstring()) -> boolean())) -> bitty:parser(bitstring()).
take_until(Predicate) ->
take_while(fun(B) -> not Predicate(B) end).