Packages

Binary and string parser combinator library for Gleam

Current section

Files

Jump to
bitty src bitty.erl
Raw

src/bitty.erl

-module(bitty).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/bitty.gleam").
-export([make_parser/1, run_parser/2, success/1, fail/1, from_result/1, map/2, then/2, one_of/1, attempt/1, cut/1, label/2, context/2, many/1, many1/1, repeat/2, optional/1, location/0, align/0, 'end'/0, advance_bits/3, read_uint/4, stop_expected/2, within_bytes/2, read_n_bytes/2, effective_byte_offset/1, within_bytes_partial/2, run/2, run_partial/2, run_with_location/2]).
-export_type([location/0, bitty_error/0, parser/1, state/0, step/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(
" Zero-copy binary parser combinators for Gleam, targeting both BEAM and\n"
" JavaScript. Build parsers by composing primitives from `bitty/bytes`,\n"
" `bitty/bits`, and `bitty/num` with the combinators in this module.\n"
" Parsers are designed for Gleam's `use` syntax.\n"
).
-type location() :: {location, integer(), integer()}.
-type bitty_error() :: {bitty_error,
location(),
list(binary()),
list(binary()),
gleam@option:option(binary())}.
-opaque parser(OZP) :: {parser, fun((state()) -> step(OZP))}.
-type state() :: {state, bitstring(), integer(), integer(), boolean()}.
-type step(OZQ) :: {continue, OZQ, state(), boolean()} |
{stop, bitty_error(), boolean(), boolean()}.
-file("src/bitty.gleam", 51).
?DOC(false).
-spec make_parser(fun((state()) -> step(OZR))) -> parser(OZR).
make_parser(F) ->
{parser, F}.
-file("src/bitty.gleam", 56).
?DOC(false).
-spec run_parser(parser(OZU), state()) -> step(OZU).
run_parser(Parser, State) ->
(erlang:element(2, Parser))(State).
-file("src/bitty.gleam", 62).
?DOC(
" Create a parser that always succeeds with the given value without\n"
" consuming any input.\n"
).
-spec success(OZX) -> parser(OZX).
success(Value) ->
{parser, fun(State) -> {continue, Value, State, false} end}.
-file("src/bitty.gleam", 68).
?DOC(
" Create a parser that always fails with the given message without\n"
" consuming any input.\n"
).
-spec fail(binary()) -> parser(any()).
fail(Message) ->
{parser,
fun(State) ->
{stop,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[],
[],
{some, Message}},
false,
erlang:element(5, State)}
end}.
-file("src/bitty.gleam", 87).
?DOC(
" Convert a `Result(a, e)` into a parser: `Ok(value)` succeeds with the\n"
" value, `Error(e)` fails with the inspected error as the message.\n"
" Useful for lifting fallible conversions (e.g. `bit_array.to_string`)\n"
" into a parser pipeline with `use` syntax.\n"
).
-spec from_result({ok, PAB} | {error, any()}) -> parser(PAB).
from_result(Result) ->
case Result of
{ok, Value} ->
success(Value);
{error, E} ->
fail(gleam@string:inspect(E))
end.
-file("src/bitty.gleam", 154).
?DOC(" Transform the result of a parser by applying `f` to the parsed value.\n").
-spec map(parser(PAW), fun((PAW) -> PAY)) -> parser(PAY).
map(Parser, F) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, Value, State1, Consumed} ->
{continue, F(Value), State1, Consumed};
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end}.
-file("src/bitty.gleam", 171).
?DOC(
" Sequence two parsers: run `parser`, then pass its result to `next` to\n"
" get the second parser. Designed for Gleam's `use` syntax:\n"
"\n"
" ```gleam\n"
" use length <- bitty.then(num.u8())\n"
" use data <- bitty.then(bytes.take(length))\n"
" bitty.success(data)\n"
" ```\n"
).
-spec then(parser(PBA), fun((PBA) -> parser(PBC))) -> parser(PBC).
then(Parser, Next) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, Value, State1, Consumed1} ->
case (erlang:element(2, (Next(Value))))(State1) of
{continue, Value2, State2, Consumed2} ->
{continue,
Value2,
State2,
Consumed1 orelse Consumed2};
{stop, Error, Consumed2@1, Committed2} ->
{stop,
Error,
Consumed1 orelse Consumed2@1,
Committed2 orelse erlang:element(5, State1)}
end;
{stop, Error@1, Consumed, Committed} ->
{stop, Error@1, Consumed, Committed}
end end}.
-file("src/bitty.gleam", 232).
-spec merge_errors(bitty_error(), bitty_error()) -> bitty_error().
merge_errors(E1, E2) ->
Loc1 = (erlang:element(2, erlang:element(2, E1)) * 8) + erlang:element(
3,
erlang:element(2, E1)
),
Loc2 = (erlang:element(2, erlang:element(2, E2)) * 8) + erlang:element(
3,
erlang:element(2, E2)
),
case gleam@int:compare(Loc1, Loc2) of
gt ->
E1;
lt ->
E2;
eq ->
{bitty_error,
erlang:element(2, E1),
lists:append(erlang:element(3, E1), erlang:element(3, E2)),
erlang:element(4, E1),
erlang:element(5, E1)}
end.
-file("src/bitty.gleam", 213).
-spec try_parsers(list(parser(PBJ)), state(), bitty_error()) -> step(PBJ).
try_parsers(Parsers, State, Last_error) ->
case Parsers of
[] ->
{stop, Last_error, false, false};
[Parser | Rest] ->
case (erlang:element(2, Parser))(State) of
{continue, _, _, _} = Success ->
Success;
{stop, Error, Consumed, Committed} ->
case Consumed orelse Committed of
true ->
{stop, Error, Consumed, Committed};
false ->
try_parsers(
Rest,
State,
merge_errors(Last_error, Error)
)
end
end
end.
-file("src/bitty.gleam", 198).
?DOC(
" Try each parser in order, returning the first success. A parser that\n"
" consumes input before failing will **not** backtrack — wrap alternatives\n"
" in `attempt` if backtracking is needed. Errors from non-consuming\n"
" failures at the same position are merged.\n"
"\n"
" ```gleam\n"
" let parser = bitty.one_of([\n"
" bitty.attempt(bytes.tag(<<0x01>>) |> bitty.map(fn(_) { \"one\" })),\n"
" bytes.tag(<<0x02>>) |> bitty.map(fn(_) { \"two\" }),\n"
" ])\n"
" let assert Ok(\"two\") = bitty.run(parser, on: <<0x02>>)\n"
" ```\n"
).
-spec one_of(list(parser(PBF))) -> parser(PBF).
one_of(Parsers) ->
{parser,
fun(State) ->
try_parsers(
Parsers,
State,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[],
[],
none}
)
end}.
-file("src/bitty.gleam", 254).
?DOC(
" Wrap a parser to allow backtracking on failure. If the inner parser\n"
" fails after consuming input, `attempt` resets the consumed flag so\n"
" that `one_of` can try the next alternative.\n"
"\n"
" ```gleam\n"
" let parser = bitty.one_of([\n"
" bitty.attempt(bytes.tag(<<0xCA, 0xFE>>)),\n"
" bytes.tag(<<0xCA, 0x11>>),\n"
" ])\n"
" let assert Ok(Nil) = bitty.run(parser, on: <<0xCA, 0x11>>)\n"
" ```\n"
).
-spec attempt(parser(PBN)) -> parser(PBN).
attempt(Parser) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, _, _, _} = Success ->
Success;
{stop, Error, _, Committed} ->
{stop, Error, false, Committed}
end end}.
-file("src/bitty.gleam", 272).
?DOC(
" Commit to the current parse path on success. After `cut`, a later\n"
" failure will not backtrack past this point, producing better error\n"
" messages. Typically used after matching a tag or discriminator.\n"
"\n"
" ```gleam\n"
" use _ <- bitty.then(bitty.cut(bytes.tag(<<0x01>>)))\n"
" use value <- bitty.then(num.u8())\n"
" bitty.success(value)\n"
" ```\n"
).
-spec cut(parser(PBQ)) -> parser(PBQ).
cut(Parser) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, Value, State1, Consumed} ->
{continue,
Value,
{state,
erlang:element(2, State1),
erlang:element(3, State1),
erlang:element(4, State1),
true},
Consumed};
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end}.
-file("src/bitty.gleam", 290).
?DOC(
" Replace the `expected` list in a parse error with `name`.\n"
" Useful for giving user-friendly names to complex parsers.\n"
"\n"
" ```gleam\n"
" let parser = num.u8() |> bitty.label(named: \"message type\")\n"
" let assert Error(e) = bitty.run(parser, on: <<>>)\n"
" assert e.expected == [\"message type\"]\n"
" ```\n"
).
-spec label(parser(PBT), binary()) -> parser(PBT).
label(Parser, Name) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, _, _, _} = Success ->
Success;
{stop, Error, Consumed, Committed} ->
{stop,
{bitty_error,
erlang:element(2, Error),
[Name],
erlang:element(4, Error),
erlang:element(5, Error)},
Consumed,
Committed}
end end}.
-file("src/bitty.gleam", 308).
?DOC(
" Push `name` onto the error context stack on failure, creating a\n"
" breadcrumb trail (e.g. `[\"TLS record\", \"handshake\", \"certificate\"]`).\n"
"\n"
" ```gleam\n"
" let parser = num.u8() |> bitty.context(in: \"header\")\n"
" let assert Error(e) = bitty.run(parser, on: <<>>)\n"
" assert e.context == [\"header\"]\n"
" ```\n"
).
-spec context(parser(PBW), binary()) -> parser(PBW).
context(Parser, Name) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, _, _, _} = Success ->
Success;
{stop, Error, Consumed, Committed} ->
{stop,
{bitty_error,
erlang:element(2, Error),
erlang:element(3, Error),
[Name | erlang:element(4, Error)],
erlang:element(5, Error)},
Consumed,
Committed}
end end}.
-file("src/bitty.gleam", 336).
-spec many_loop(parser(PCD), state(), list(PCD), boolean()) -> step(list(PCD)).
many_loop(Parser, State, Acc, Any_consumed) ->
case (erlang:element(2, Parser))(
{state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
false}
) of
{continue, Value, State1, true} ->
many_loop(
Parser,
{state,
erlang:element(2, State1),
erlang:element(3, State1),
erlang:element(4, State1),
erlang:element(5, State1) orelse erlang:element(5, State)},
[Value | Acc],
true
);
{continue, _, _, false} ->
{stop,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[<<"consuming parser in many"/utf8>>],
[],
none},
Any_consumed,
erlang:element(5, State)};
{stop, _, false, false} ->
{continue, lists:reverse(Acc), State, Any_consumed};
{stop, Error, Consumed, Committed} ->
{stop,
Error,
Any_consumed orelse Consumed,
Committed orelse erlang:element(5, State)}
end.
-file("src/bitty.gleam", 332).
?DOC(
" Repeat a parser zero or more times, collecting results into a list.\n"
" The inner parser **must** consume input on each iteration; a\n"
" non-consuming parser causes an immediate error to prevent infinite loops.\n"
" Stops when the parser fails without consuming input.\n"
"\n"
" ```gleam\n"
" let assert Ok(values) =\n"
" bitty.run(bitty.many(num.u8()), on: <<1, 2, 3>>)\n"
" assert values == [1, 2, 3]\n"
" ```\n"
).
-spec many(parser(PBZ)) -> parser(list(PBZ)).
many(Parser) ->
{parser, fun(State) -> many_loop(Parser, State, [], false) end}.
-file("src/bitty.gleam", 369).
?DOC(" Like `many`, but requires at least one successful match.\n").
-spec many1(parser(PCI)) -> parser(list(PCI)).
many1(Parser) ->
_pipe = Parser,
then(_pipe, fun(First) -> _pipe@1 = many(Parser),
map(_pipe@1, fun(Rest) -> [First | Rest] end) end).
-file("src/bitty.gleam", 379).
-spec repeat_loop(parser(PCQ), state(), list(PCQ), integer(), boolean()) -> step(list(PCQ)).
repeat_loop(Parser, State, Acc, Remaining, Any_consumed) ->
case Remaining =< 0 of
true ->
{continue, lists:reverse(Acc), State, Any_consumed};
false ->
case (erlang:element(2, Parser))(State) of
{continue, Value, State1, Consumed} ->
repeat_loop(
Parser,
State1,
[Value | Acc],
Remaining - 1,
Any_consumed orelse Consumed
);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Any_consumed orelse Consumed@1, Committed}
end
end.
-file("src/bitty.gleam", 375).
?DOC(" Run a parser exactly `count` times, collecting results into a list.\n").
-spec repeat(parser(PCM), integer()) -> parser(list(PCM)).
repeat(Parser, Count) ->
{parser, fun(State) -> repeat_loop(Parser, State, [], Count, false) end}.
-file("src/bitty.gleam", 406).
?DOC(
" Try a parser, returning `Some(value)` on success or `None` if it fails\n"
" without consuming input. A consuming failure still propagates.\n"
).
-spec optional(parser(PCV)) -> parser(gleam@option:option(PCV)).
optional(Parser) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, Value, State1, Consumed} ->
{continue, {some, Value}, State1, Consumed};
{stop, _, false, false} ->
{continue, none, State, false};
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end}.
-file("src/bitty.gleam", 418).
?DOC(" Return the current `Location` in the input without consuming anything.\n").
-spec location() -> parser(location()).
location() ->
{parser,
fun(State) ->
{continue,
{location, erlang:element(3, State), erlang:element(4, State)},
State,
false}
end}.
-file("src/bitty.gleam", 431).
?DOC(
" Skip remaining bits in the current byte to reach the next byte boundary.\n"
" If already aligned, this is a no-op. Use after bit-level parsing to\n"
" resume byte-aligned operations.\n"
).
-spec align() -> parser(nil).
align() ->
{parser, fun(State) -> case erlang:element(4, State) =:= 0 of
true ->
{continue, nil, State, false};
false ->
{continue,
nil,
{state,
erlang:element(2, State),
erlang:element(3, State) + 1,
0,
erlang:element(5, State)},
true}
end end}.
-file("src/bitty.gleam", 447).
?DOC(
" Succeed only if all input has been consumed. Fails with\n"
" `\"end of input\"` expected if bytes remain.\n"
).
-spec 'end'() -> parser(nil).
'end'() ->
{parser,
fun(State) ->
Remaining = erlang:byte_size(erlang:element(2, State)) - erlang:element(
3,
State
),
case (Remaining =:= 0) andalso (erlang:element(4, State) =:= 0) of
true ->
{continue, nil, State, false};
false ->
{stop,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[<<"end of input"/utf8>>],
[],
none},
false,
erlang:element(5, State)}
end
end}.
-file("src/bitty.gleam", 527).
-spec within_bytes_check(state(), integer(), PDO, state()) -> step(PDO).
within_bytes_check(State, Byte_len, Value, Inner_end) ->
case (erlang:element(3, Inner_end) =:= Byte_len) andalso (erlang:element(
4,
Inner_end
)
=:= 0) of
true ->
{continue,
Value,
{state,
erlang:element(2, State),
erlang:element(3, State) + Byte_len,
erlang:element(4, State),
erlang:element(5, State)},
Byte_len > 0};
false ->
{stop,
{bitty_error,
{location,
erlang:element(3, State) + erlang:element(3, Inner_end),
erlang:element(4, Inner_end)},
[<<<<"complete consumption of "/utf8,
(erlang:integer_to_binary(Byte_len))/binary>>/binary,
"-byte window"/utf8>>],
[],
none},
true,
erlang:element(5, State)}
end.
-file("src/bitty.gleam", 583).
-spec within_bytes_stop(state(), bitty_error(), boolean(), boolean()) -> step(any()).
within_bytes_stop(State, Error, Consumed, Committed) ->
{stop,
{bitty_error,
{location,
erlang:element(3, State) + erlang:element(
2,
erlang:element(2, Error)
),
erlang:element(3, erlang:element(2, Error))},
erlang:element(3, Error),
erlang:element(4, Error),
erlang:element(5, Error)},
Consumed,
Committed orelse erlang:element(5, State)}.
-file("src/bitty.gleam", 675).
?DOC(false).
-spec advance_bits(integer(), integer(), integer()) -> {integer(), integer()}.
advance_bits(Byte_offset, Bit_offset, Count) ->
Total_bits = Bit_offset + Count,
{Byte_offset + (Total_bits div 8), Total_bits rem 8}.
-file("src/bitty.gleam", 634).
?DOC(false).
-spec read_uint(state(), integer(), integer(), boolean()) -> step(integer()).
read_uint(State, Remaining, Acc, Consumed) ->
case Remaining =< 0 of
true ->
{continue, Acc, State, Consumed};
false ->
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
erlang:element(3, State),
1
) of
{ok, <<Byte>>} ->
Available = 8 - erlang:element(4, State),
To_read = gleam@int:min(Available, Remaining),
Shift = Available - To_read,
Mask = erlang:'bsl'(1, To_read) - 1,
Bits_val = erlang:'band'(erlang:'bsr'(Byte, Shift), Mask),
New_acc = erlang:'bor'(erlang:'bsl'(Acc, To_read), Bits_val),
{New_byte, New_bit} = advance_bits(
erlang:element(3, State),
erlang:element(4, State),
To_read
),
New_state = {state,
erlang:element(2, State),
New_byte,
New_bit,
erlang:element(5, State)},
read_uint(New_state, Remaining - To_read, New_acc, Consumed);
_ ->
{stop,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[<<(erlang:integer_to_binary(Remaining))/binary,
" more bits"/utf8>>],
[],
none},
Consumed,
erlang:element(5, State)}
end
end.
-file("src/bitty.gleam", 709).
-spec read_n_bytes_unaligned(state(), integer(), bitstring(), boolean()) -> step(bitstring()).
read_n_bytes_unaligned(State, Remaining, Acc, Consumed) ->
case Remaining =< 0 of
true ->
{continue, Acc, State, Consumed};
false ->
case read_uint(State, 8, 0, Consumed) of
{continue, Byte_val, New_state, _} ->
read_n_bytes_unaligned(
New_state,
Remaining - 1,
<<Acc/bitstring, Byte_val>>,
Consumed
);
{stop, Error, Consumed2, Committed} ->
{stop, Error, Consumed2, Committed}
end
end.
-file("src/bitty.gleam", 732).
?DOC(false).
-spec stop_expected(state(), binary()) -> step(any()).
stop_expected(State, Expected) ->
{stop,
{bitty_error,
{location, erlang:element(3, State), erlang:element(4, State)},
[Expected],
[],
none},
false,
erlang:element(5, State)}.
-file("src/bitty.gleam", 489).
-spec within_bytes_setup(
state(),
integer(),
parser(PDF),
fun((state(), bitstring(), step(PDF)) -> step(PDI))
) -> step(PDI).
within_bytes_setup(State, Byte_len, Inner, On_window) ->
Remaining = erlang:byte_size(erlang:element(2, State)) - erlang:element(
3,
State
),
case Remaining < Byte_len of
true ->
stop_expected(
State,
<<<<"at least "/utf8,
(erlang:integer_to_binary(Byte_len))/binary>>/binary,
" bytes"/utf8>>
);
false ->
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
erlang:element(3, State),
Byte_len
) of
{ok, Window} ->
Inner_state = {state, Window, 0, 0, false},
On_window(
State,
Window,
(erlang:element(2, Inner))(Inner_state)
);
_ ->
stop_expected(State, <<"valid slice"/utf8>>)
end
end.
-file("src/bitty.gleam", 516).
-spec within_bytes_run(state(), integer(), parser(PDL)) -> step(PDL).
within_bytes_run(State, Byte_len, Inner) ->
within_bytes_setup(
State,
Byte_len,
Inner,
fun(St, _, Result) -> case Result of
{continue, Value, Inner_end, _} ->
within_bytes_check(St, Byte_len, Value, Inner_end);
{stop, Error, Consumed, Committed} ->
within_bytes_stop(St, Error, Consumed, Committed)
end end
).
-file("src/bitty.gleam", 476).
?DOC(
" Run `inner` on a zero-copy window of exactly `byte_len` bytes.\n"
" The inner parser must consume the entire window or the parse fails.\n"
" Requires byte alignment.\n"
"\n"
" ```gleam\n"
" use length <- bitty.then(num.u8())\n"
" use value <- bitty.then(bitty.within_bytes(length, run: bytes.rest()))\n"
" bitty.success(value)\n"
" ```\n"
).
-spec within_bytes(integer(), parser(PDC)) -> parser(PDC).
within_bytes(Byte_len, Inner) ->
{parser,
fun(State) ->
gleam@bool:guard(
Byte_len < 0,
stop_expected(State, <<"non-negative byte length"/utf8>>),
fun() -> case erlang:element(4, State) /= 0 of
true ->
stop_expected(State, <<"byte alignment"/utf8>>);
false ->
within_bytes_run(State, Byte_len, Inner)
end end
)
end}.
-file("src/bitty.gleam", 697).
-spec read_n_bytes_aligned(state(), integer()) -> step(bitstring()).
read_n_bytes_aligned(State, Count) ->
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
erlang:element(3, State),
Count
) of
{ok, Bytes} ->
{continue,
Bytes,
{state,
erlang:element(2, State),
erlang:element(3, State) + Count,
erlang:element(4, State),
erlang:element(5, State)},
Count > 0};
_ ->
stop_expected(
State,
<<(erlang:integer_to_binary(Count))/binary, " bytes"/utf8>>
)
end.
-file("src/bitty.gleam", 685).
?DOC(false).
-spec read_n_bytes(state(), integer()) -> step(bitstring()).
read_n_bytes(State, Count) ->
gleam@bool:guard(
Count < 0,
stop_expected(State, <<"non-negative byte count"/utf8>>),
fun() -> case erlang:element(4, State) =:= 0 of
true ->
read_n_bytes_aligned(State, Count);
false ->
read_n_bytes_unaligned(State, Count, <<>>, Count > 0)
end end
).
-file("src/bitty.gleam", 746).
?DOC(false).
-spec effective_byte_offset(state()) -> integer().
effective_byte_offset(State) ->
case erlang:element(4, State) /= 0 of
true ->
erlang:element(3, State) + 1;
false ->
erlang:element(3, State)
end.
-file("src/bitty.gleam", 561).
-spec within_bytes_partial_check(state(), integer(), PDQ, state(), bitstring()) -> step({PDQ,
bitstring()}).
within_bytes_partial_check(State, Byte_len, Value, Inner_end, Window) ->
Effective_inner_offset = effective_byte_offset(Inner_end),
Unconsumed = Byte_len - Effective_inner_offset,
Leftover = case gleam_stdlib:bit_array_slice(
Window,
Effective_inner_offset,
Unconsumed
) of
{ok, Rest} ->
Rest;
_ ->
<<>>
end,
{continue,
{Value, Leftover},
{state,
erlang:element(2, State),
erlang:element(3, State) + Byte_len,
erlang:element(4, State),
erlang:element(5, State)},
Byte_len > 0}.
-file("src/bitty.gleam", 618).
-spec within_bytes_partial_run(state(), integer(), parser(PDX)) -> step({PDX,
bitstring()}).
within_bytes_partial_run(State, Byte_len, Inner) ->
within_bytes_setup(
State,
Byte_len,
Inner,
fun(St, Window, Result) -> case Result of
{continue, Value, Inner_end, _} ->
within_bytes_partial_check(
St,
Byte_len,
Value,
Inner_end,
Window
);
{stop, Error, Consumed, Committed} ->
within_bytes_stop(St, Error, Consumed, Committed)
end end
).
-file("src/bitty.gleam", 602).
?DOC(
" Like `within_bytes`, but the inner parser may stop early. Returns the\n"
" parsed value and any unconsumed bytes from the window as a tuple.\n"
" Requires byte alignment.\n"
).
-spec within_bytes_partial(integer(), parser(PDU)) -> parser({PDU, bitstring()}).
within_bytes_partial(Byte_len, Inner) ->
{parser,
fun(State) ->
gleam@bool:guard(
Byte_len < 0,
stop_expected(State, <<"non-negative byte length"/utf8>>),
fun() -> case erlang:element(4, State) /= 0 of
true ->
stop_expected(State, <<"byte alignment"/utf8>>);
false ->
within_bytes_partial_run(State, Byte_len, Inner)
end end
)
end}.
-file("src/bitty.gleam", 753).
-spec remaining_slice(state()) -> bitstring().
remaining_slice(State) ->
Offset = effective_byte_offset(State),
Remaining = erlang:byte_size(erlang:element(2, State)) - Offset,
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
Offset,
Remaining
) of
{ok, Rest} ->
Rest;
_ ->
<<>>
end.
-file("src/bitty.gleam", 762).
-spec new_state(bitstring()) -> state().
new_state(Input) ->
{state, Input, 0, 0, false}.
-file("src/bitty.gleam", 145).
-spec do_run(parser(PAS), bitstring()) -> {ok, {PAS, state()}} |
{error, bitty_error()}.
do_run(Parser, Input) ->
State = new_state(Input),
case (erlang:element(2, Parser))(State) of
{continue, Value, Final_state, _} ->
{ok, {Value, Final_state}};
{stop, Error, _, _} ->
{error, Error}
end.
-file("src/bitty.gleam", 101).
?DOC(
" Run a parser on the given input, requiring it to consume all bytes.\n"
" Returns `Error` if parsing fails or if unconsumed input remains.\n"
"\n"
" ```gleam\n"
" let assert Ok(byte) = bitty.run(num.u8(), on: <<0xFF>>)\n"
" assert byte == 255\n"
" ```\n"
).
-spec run(parser(PAG), bitstring()) -> {ok, PAG} | {error, bitty_error()}.
run(Parser, Input) ->
gleam@result:'try'(
do_run(Parser, Input),
fun(_use0) ->
{Value, Final_state} = _use0,
Remaining = erlang:byte_size(erlang:element(2, Final_state)) - erlang:element(
3,
Final_state
),
case (Remaining =:= 0) andalso (erlang:element(4, Final_state) =:= 0) of
true ->
{ok, Value};
false ->
{error,
{bitty_error,
{location,
erlang:element(3, Final_state),
erlang:element(4, Final_state)},
[<<"end of input"/utf8>>],
[],
none}}
end
end
).
-file("src/bitty.gleam", 126).
?DOC(
" Run a parser on the given input, returning the parsed value and any\n"
" unconsumed bytes. Does not require all input to be consumed.\n"
"\n"
" ```gleam\n"
" let assert Ok(#(byte, rest)) =\n"
" bitty.run_partial(num.u8(), on: <<0xAA, 0xBB>>)\n"
" assert byte == 0xAA\n"
" assert rest == <<0xBB>>\n"
" ```\n"
).
-spec run_partial(parser(PAK), bitstring()) -> {ok, {PAK, bitstring()}} |
{error, bitty_error()}.
run_partial(Parser, Input) ->
gleam@result:'try'(
do_run(Parser, Input),
fun(_use0) ->
{Value, Final_state} = _use0,
{ok, {Value, remaining_slice(Final_state)}}
end
).
-file("src/bitty.gleam", 136).
?DOC(
" Like `run_partial`, but also returns the `Location` where the parser\n"
" stopped. Useful for incremental or streaming parsing.\n"
).
-spec run_with_location(parser(PAO), bitstring()) -> {ok,
{PAO, location(), bitstring()}} |
{error, bitty_error()}.
run_with_location(Parser, Input) ->
gleam@result:'try'(
do_run(Parser, Input),
fun(_use0) ->
{Value, Final_state} = _use0,
Loc = {location,
erlang:element(3, Final_state),
erlang:element(4, Final_state)},
{ok, {Value, Loc, remaining_slice(Final_state)}}
end
).