-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, <>, 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, <>, 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, <>, 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).