Current section
Files
Jump to
Current section
Files
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, replace/2, then/2, one_of/1, attempt/1, cut/1, label/2, context/2, many_until/2, fold/3, many/1, many1/1, fold1/3, repeat/2, length_repeat/2, preceded/2, terminated/2, delimited/3, pair/2, separated_pair/3, separated1/2, separated/2, optional/1, verify/2, 'not'/1, 'cond'/2, location/0, align/0, 'end'/0, advance_bits/3, read_uint/4, stop_expected/2, read_n_bytes/2, length_take/1, require_aligned/2, within_bytes/2, within_bytes_partial/2, capture/1, length_within/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", 158).
?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", 174).
?DOC(
" Replace the result of a parser with a fixed value, discarding the\n"
" original result. Useful with `tag` patterns.\n"
"\n"
" ```gleam\n"
" let parser = bytes.tag(<<0x01>>) |> bitty.replace(with: \"one\")\n"
" let assert Ok(\"one\") = bitty.run(parser, on: <<0x01>>)\n"
" ```\n"
).
-spec replace(parser(any()), PBC) -> parser(PBC).
replace(Parser, Value) ->
_pipe = Parser,
map(_pipe, fun(_) -> Value end).
-file("src/bitty.gleam", 186).
?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(PBE), fun((PBE) -> parser(PBG))) -> parser(PBG).
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", 247).
-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", 228).
-spec try_parsers(list(parser(PBN)), state(), bitty_error()) -> step(PBN).
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", 213).
?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(PBJ))) -> parser(PBJ).
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", 269).
?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(PBR)) -> parser(PBR).
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", 287).
?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(PBU)) -> parser(PBU).
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", 305).
?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(PBX), binary()) -> parser(PBX).
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", 323).
?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(PCA), binary()) -> parser(PCA).
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", 382).
-spec many_until_loop(parser(PCR), parser(PCT), state(), list(PCR), boolean()) -> step({list(PCR),
PCT}).
many_until_loop(Parser, Terminator, State, Acc, Any_consumed) ->
case (erlang:element(2, Terminator))(
{state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
false}
) of
{continue, Term_value, State1, Consumed} ->
{continue,
{lists:reverse(Acc), Term_value},
{state,
erlang:element(2, State1),
erlang:element(3, State1),
erlang:element(4, State1),
erlang:element(5, State1) orelse erlang:element(5, State)},
Any_consumed orelse Consumed};
{stop, _, false, false} ->
case (erlang:element(2, Parser))(State) of
{continue, Value, State1@1, true} ->
many_until_loop(
Parser,
Terminator,
{state,
erlang:element(2, State1@1),
erlang:element(3, State1@1),
erlang:element(4, State1@1),
erlang:element(5, State1@1) orelse erlang:element(
5,
State
)},
[Value | Acc],
true
);
{continue, _, _, false} ->
{stop,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[<<"consuming parser"/utf8>>],
[],
none},
Any_consumed,
erlang:element(5, State)};
{stop, Error, Consumed@1, Committed} ->
{stop,
Error,
Any_consumed orelse Consumed@1,
Committed orelse erlang:element(5, State)}
end;
{stop, Error@1, Consumed@2, Committed@1} ->
{stop,
Error@1,
Any_consumed orelse Consumed@2,
Committed@1 orelse erlang:element(5, State)}
end.
-file("src/bitty.gleam", 375).
?DOC(
" Repeat a parser until a terminator succeeds, returning the collected\n"
" values and the terminator's result as a tuple. The terminator is tried\n"
" first each iteration; on failure the item parser runs. The item parser\n"
" must consume input on each iteration to prevent infinite loops.\n"
"\n"
" ```gleam\n"
" let parser = bitty.many_until(num.u8(), until: bytes.tag(<<0x00>>))\n"
" let assert Ok(#(values, Nil)) =\n"
" bitty.run(parser, on: <<1, 2, 3, 0x00>>)\n"
" assert values == [1, 2, 3]\n"
" ```\n"
).
-spec many_until(parser(PCL), parser(PCN)) -> parser({list(PCL), PCN}).
many_until(Parser, Terminator) ->
{parser,
fun(State) -> many_until_loop(Parser, Terminator, State, [], false) end}.
-file("src/bitty.gleam", 440).
-spec fold_loop(parser(PDC), fun((PDE, PDC) -> PDE), state(), PDE, boolean()) -> step(PDE).
fold_loop(Parser, F, 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} ->
fold_loop(
Parser,
F,
{state,
erlang:element(2, State1),
erlang:element(3, State1),
erlang:element(4, State1),
erlang:element(5, State1) orelse erlang:element(5, State)},
F(Acc, Value),
true
);
{continue, _, _, false} ->
{stop,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[<<"consuming parser"/utf8>>],
[],
none},
Any_consumed,
erlang:element(5, State)};
{stop, _, false, false} ->
{continue, 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", 432).
?DOC(
" Like `many` but accumulates results with a function instead of\n"
" building a list. Runs the parser zero or more times.\n"
"\n"
" ```gleam\n"
" let parser = bitty.fold(num.u8(), from: 0, with: fn(acc, x) { acc + x })\n"
" let assert Ok(6) = bitty.run(parser, on: <<1, 2, 3>>)\n"
" ```\n"
).
-spec fold(parser(PCY), PDA, fun((PDA, PCY) -> PDA)) -> parser(PDA).
fold(Parser, Initial, F) ->
{parser, fun(State) -> fold_loop(Parser, F, State, Initial, false) end}.
-file("src/bitty.gleam", 347).
?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(PCD)) -> parser(list(PCD)).
many(Parser) ->
_pipe = fold(Parser, [], fun(Acc, X) -> [X | Acc] end),
map(_pipe, fun lists:reverse/1).
-file("src/bitty.gleam", 359).
?DOC(
" Like `many`, but requires at least one successful match.\n"
"\n"
" ```gleam\n"
" let assert Ok(values) =\n"
" bitty.run(bitty.many1(num.u8()), on: <<1, 2, 3>>)\n"
" assert values == [1, 2, 3]\n"
" ```\n"
).
-spec many1(parser(PCH)) -> parser(list(PCH)).
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", 479).
?DOC(
" Like `fold` but requires at least one successful match.\n"
"\n"
" ```gleam\n"
" let parser = bitty.fold1(num.u8(), from: 0, with: fn(acc, x) { acc + x })\n"
" let assert Ok(6) = bitty.run(parser, on: <<1, 2, 3>>)\n"
" ```\n"
).
-spec fold1(parser(PDG), PDI, fun((PDI, PDG) -> PDI)) -> parser(PDI).
fold1(Parser, Initial, F) ->
_pipe = Parser,
then(_pipe, fun(First) -> fold(Parser, F(Initial, First), F) end).
-file("src/bitty.gleam", 498).
-spec repeat_loop(parser(PDO), state(), list(PDO), integer(), boolean()) -> step(list(PDO)).
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", 494).
?DOC(
" Run a parser exactly `count` times, collecting results into a list.\n"
"\n"
" ```gleam\n"
" let assert Ok(values) =\n"
" bitty.run(bitty.repeat(num.u8(), times: 2), on: <<1, 2, 3>>)\n"
" assert values == [1, 2]\n"
" ```\n"
).
-spec repeat(parser(PDK), integer()) -> parser(list(PDK)).
repeat(Parser, Count) ->
{parser, fun(State) -> repeat_loop(Parser, State, [], Count, false) end}.
-file("src/bitty.gleam", 531).
?DOC(
" Parse a count, then run a parser that many times. Dynamic version\n"
" of `repeat`.\n"
"\n"
" ```gleam\n"
" let parser = bitty.length_repeat(num.u8(), run: num.u8())\n"
" let assert Ok(values) = bitty.run(parser, on: <<3, 10, 20, 30>>)\n"
" assert values == [10, 20, 30]\n"
" ```\n"
).
-spec length_repeat(parser(integer()), parser(PDU)) -> parser(list(PDU)).
length_repeat(Count, Parser) ->
_pipe = Count,
then(_pipe, fun(N) -> repeat(Parser, N) end).
-file("src/bitty.gleam", 545).
?DOC(
" Run `prefix` then `parser`, discarding the prefix result and returning\n"
" only the parser's value.\n"
"\n"
" ```gleam\n"
" use value <- bitty.then(bitty.preceded(bytes.tag(<<0x00>>), num.u8()))\n"
" bitty.success(value)\n"
" ```\n"
).
-spec preceded(parser(any()), parser(PEA)) -> parser(PEA).
preceded(Prefix, Parser) ->
_pipe = Prefix,
then(_pipe, fun(_) -> Parser end).
-file("src/bitty.gleam", 556).
?DOC(
" Run `parser` then `suffix`, discarding the suffix result and returning\n"
" only the parser's value.\n"
"\n"
" ```gleam\n"
" use value <- bitty.then(bitty.terminated(num.u8(), bytes.tag(<<0x00>>)))\n"
" bitty.success(value)\n"
" ```\n"
).
-spec terminated(parser(PED), parser(any())) -> parser(PED).
terminated(Parser, Suffix) ->
_pipe = Parser,
then(_pipe, fun(Value) -> _pipe@1 = Suffix,
replace(_pipe@1, Value) end).
-file("src/bitty.gleam", 571).
?DOC(
" Run `open`, `parser`, then `close`, returning only the parser's value.\n"
"\n"
" ```gleam\n"
" let parser = bitty.delimited(\n"
" bytes.tag(<<0x28>>),\n"
" num.u8(),\n"
" bytes.tag(<<0x29>>),\n"
" )\n"
" let assert Ok(value) = bitty.run(parser, on: <<0x28, 42, 0x29>>)\n"
" assert value == 42\n"
" ```\n"
).
-spec delimited(parser(any()), parser(PEK), parser(any())) -> parser(PEK).
delimited(Open, Parser, Close) ->
preceded(Open, terminated(Parser, Close)).
-file("src/bitty.gleam", 586).
?DOC(
" Run two parsers in sequence and return their results as a tuple.\n"
"\n"
" ```gleam\n"
" let parser = bitty.pair(num.u8(), num.u8())\n"
" let assert Ok(value) = bitty.run(parser, on: <<1, 2>>)\n"
" assert value == #(1, 2)\n"
" ```\n"
).
-spec pair(parser(PEP), parser(PER)) -> parser({PEP, PER}).
pair(First, Second) ->
_pipe = First,
then(_pipe, fun(A) -> _pipe@1 = Second,
map(_pipe@1, fun(B) -> {A, B} end) end).
-file("src/bitty.gleam", 601).
?DOC(
" Run two parsers separated by a third, discarding the separator's result.\n"
"\n"
" ```gleam\n"
" let parser = bitty.separated_pair(\n"
" num.u8(),\n"
" by: bytes.tag(<<0x2C>>),\n"
" then: num.u8(),\n"
" )\n"
" let assert Ok(value) = bitty.run(parser, on: <<1, 0x2C, 2>>)\n"
" assert value == #(1, 2)\n"
" ```\n"
).
-spec separated_pair(parser(PEU), parser(any()), parser(PEY)) -> parser({PEU,
PEY}).
separated_pair(First, Separator, Second) ->
_pipe = First,
then(_pipe, fun(A) -> _pipe@1 = preceded(Separator, Second),
map(_pipe@1, fun(C) -> {A, C} end) end).
-file("src/bitty.gleam", 641).
?DOC(
" Like `separated`, but requires at least one item.\n"
"\n"
" A trailing separator (one not followed by a valid item) is left\n"
" unconsumed. Compose with `end()` if you need to ensure all input\n"
" is consumed.\n"
"\n"
" ```gleam\n"
" let parser = bitty.separated1(num.u8(), by: bytes.tag(<<0x2C>>))\n"
" let assert Ok(values) = bitty.run(parser, on: <<1, 0x2C, 2, 0x2C, 3>>)\n"
" assert values == [1, 2, 3]\n"
" ```\n"
).
-spec separated1(parser(PFH), parser(any())) -> parser(list(PFH)).
separated1(Parser, Separator) ->
_pipe = Parser,
then(
_pipe,
fun(First) -> _pipe@1 = many(attempt(preceded(Separator, Parser))),
map(_pipe@1, fun(Rest) -> [First | Rest] end) end
).
-file("src/bitty.gleam", 623).
?DOC(
" Parse zero or more occurrences of `parser` separated by `separator`.\n"
" The separator parser's result is discarded. Returns a list of the\n"
" parsed values. Succeeds with an empty list if the first item fails\n"
" without consuming input.\n"
"\n"
" A trailing separator (one not followed by a valid item) is left\n"
" unconsumed. Compose with `end()` if you need to ensure all input\n"
" is consumed.\n"
"\n"
" ```gleam\n"
" let parser = bitty.separated(num.u8(), by: bytes.tag(<<0x2C>>))\n"
" let assert Ok(values) = bitty.run(parser, on: <<1, 0x2C, 2, 0x2C, 3>>)\n"
" assert values == [1, 2, 3]\n"
" ```\n"
).
-spec separated(parser(PFB), parser(any())) -> parser(list(PFB)).
separated(Parser, Separator) ->
one_of([separated1(Parser, Separator), success([])]).
-file("src/bitty.gleam", 657).
?DOC(
" Try a parser, returning `Some(value)` on success or `None` if it fails\n"
" without consuming input. A consuming failure still propagates.\n"
"\n"
" ```gleam\n"
" let assert Ok(value) =\n"
" bitty.run(bitty.optional(num.u8()), on: <<42>>)\n"
" assert value == Some(42)\n"
" ```\n"
).
-spec optional(parser(PFN)) -> parser(gleam@option:option(PFN)).
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", 676).
?DOC(
" Run a parser and then check the result against a predicate. If the\n"
" predicate returns `False`, the parse fails with `\"verify\"` expected.\n"
"\n"
" ```gleam\n"
" let parser = num.u8() |> bitty.verify(with: fn(x) { x > 0 })\n"
" let assert Ok(42) = bitty.run(parser, on: <<42>>)\n"
" let assert Error(_) = bitty.run(parser, on: <<0>>)\n"
" ```\n"
).
-spec verify(parser(PFR), fun((PFR) -> boolean())) -> parser(PFR).
verify(Parser, Predicate) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, Value, State1, Consumed} ->
case Predicate(Value) of
true ->
{continue, Value, State1, Consumed};
false ->
{stop,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[<<"verify"/utf8>>],
[],
none},
Consumed,
erlang:element(5, State1)}
end;
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end}.
-file("src/bitty.gleam", 710).
?DOC(
" Negative lookahead: succeeds with `Nil` if the child parser fails,\n"
" fails if the child parser succeeds. Never consumes input.\n"
"\n"
" ```gleam\n"
" let parser = {\n"
" use _ <- bitty.then(bitty.not(bytes.tag(<<0x00>>)))\n"
" num.u8()\n"
" }\n"
" let assert Ok(0x42) = bitty.run(parser, on: <<0x42>>)\n"
" let assert Error(_) = bitty.run(parser, on: <<0x00>>)\n"
" ```\n"
).
-spec 'not'(parser(any())) -> parser(nil).
'not'(Parser) ->
{parser, fun(State) -> case (erlang:element(2, Parser))(State) of
{continue, _, _, _} ->
{stop,
{bitty_error,
{location,
erlang:element(3, State),
erlang:element(4, State)},
[],
[],
none},
false,
erlang:element(5, State)};
{stop, _, _, _} ->
{continue, nil, State, false}
end end}.
-file("src/bitty.gleam", 737).
?DOC(
" Conditionally run a parser. If `condition` is `True`, run the parser\n"
" and wrap the result in `Some`. If `False`, return `None` without\n"
" consuming input.\n"
"\n"
" ```gleam\n"
" let parser = bitty.cond(when: True, run: num.u8())\n"
" let assert Ok(Some(42)) = bitty.run(parser, on: <<42>>)\n"
" ```\n"
).
-spec 'cond'(boolean(), parser(PFX)) -> parser(gleam@option:option(PFX)).
'cond'(Condition, Parser) ->
case Condition of
true ->
_pipe = Parser,
map(_pipe, fun(Field@0) -> {some, Field@0} end);
false ->
success(none)
end.
-file("src/bitty.gleam", 745).
?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", 759).
?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", 779).
?DOC(
" Succeed only if all input has been consumed. Fails with\n"
" `\"end of input\"` expected if bytes remain.\n"
"\n"
" ```gleam\n"
" let assert Ok(Nil) = bitty.run(bitty.end(), on: <<>>)\n"
" ```\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", 857).
-spec within_bytes_check(state(), integer(), PGQ, state()) -> step(PGQ).
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", 891).
-spec within_bytes_partial_check(state(), integer(), PGS, state(), bitstring()) -> step({PGS,
bitstring()}).
within_bytes_partial_check(State, Byte_len, Value, Inner_end, Window) ->
Leftover = case erlang:element(4, Inner_end) /= 0 of
true ->
Available = 8 - erlang:element(4, Inner_end),
Partial = case gleam_stdlib:bit_array_slice(
Window,
erlang:element(3, Inner_end),
1
) of
{ok, <<Byte>>} ->
Mask = erlang:'bsl'(1, Available) - 1,
Bits_val = erlang:'band'(Byte, Mask),
<<Bits_val:(lists:max([(Available), 0]))>>;
_ ->
<<>>
end,
Full_offset = erlang:element(3, Inner_end) + 1,
Remaining = Byte_len - Full_offset,
case gleam_stdlib:bit_array_slice(Window, Full_offset, Remaining) of
{ok, Rest} ->
<<Partial/bitstring, Rest/bitstring>>;
_ ->
Partial
end;
false ->
Unconsumed = Byte_len - erlang:element(3, Inner_end),
case gleam_stdlib:bit_array_slice(
Window,
erlang:element(3, Inner_end),
Unconsumed
) of
{ok, Rest@1} ->
Rest@1;
_ ->
<<>>
end
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", 931).
-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", 1087).
?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", 1046).
?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", 1121).
-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", 1144).
?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", 819).
-spec within_bytes_setup(
state(),
integer(),
parser(PGH),
fun((state(), bitstring(), step(PGH)) -> step(PGK))
) -> step(PGK).
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", 846).
-spec within_bytes_run(state(), integer(), parser(PGN)) -> step(PGN).
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", 965).
-spec within_bytes_partial_run(state(), integer(), parser(PGZ)) -> step({PGZ,
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", 1109).
-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", 1097).
?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", 1041).
?DOC(
" Parse a byte count, then take that many bytes as a `BitArray`.\n"
" Dynamic version of `bytes.take`.\n"
"\n"
" ```gleam\n"
" let parser = bitty.length_take(num.u8())\n"
" let assert Ok(data) = bitty.run(parser, on: <<3, 0xAA, 0xBB, 0xCC>>)\n"
" assert data == <<0xAA, 0xBB, 0xCC>>\n"
" ```\n"
).
-spec length_take(parser(integer())) -> parser(bitstring()).
length_take(Count) ->
_pipe = Count,
then(
_pipe,
fun(N) ->
make_parser(fun(_capture) -> read_n_bytes(_capture, N) end)
end
).
-file("src/bitty.gleam", 1158).
?DOC(false).
-spec require_aligned(state(), fun(() -> step(PHR))) -> step(PHR).
require_aligned(State, Continue) ->
case erlang:element(4, State) /= 0 of
true ->
stop_expected(State, <<"byte alignment"/utf8>>);
false ->
Continue()
end.
-file("src/bitty.gleam", 808).
?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(PGE)) -> parser(PGE).
within_bytes(Byte_len, Inner) ->
{parser,
fun(State) ->
gleam@bool:guard(
Byte_len < 0,
stop_expected(State, <<"non-negative byte length"/utf8>>),
fun() ->
require_aligned(
State,
fun() -> within_bytes_run(State, Byte_len, Inner) end
)
end
)
end}.
-file("src/bitty.gleam", 951).
?DOC(
" Like `within_bytes`, but the inner parser may stop early. Returns the\n"
" parsed value and any unconsumed input from the window as a tuple.\n"
" Requires byte alignment. The returned `BitArray` may not be byte-aligned\n"
" when bit-level parsers are used.\n"
).
-spec within_bytes_partial(integer(), parser(PGW)) -> parser({PGW, 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() ->
require_aligned(
State,
fun() ->
within_bytes_partial_run(State, Byte_len, Inner)
end
)
end
)
end}.
-file("src/bitty.gleam", 992).
?DOC(
" Run a parser and return the raw bytes it consumed, discarding the\n"
" parsed value. Requires byte alignment at both start and end.\n"
"\n"
" ```gleam\n"
" let inner = {\n"
" use _ <- bitty.then(num.u8())\n"
" use _ <- bitty.then(num.u8())\n"
" bitty.success(Nil)\n"
" }\n"
" let assert Ok(raw) = bitty.run(bitty.capture(inner), on: <<0xCA, 0xFE>>)\n"
" assert raw == <<0xCA, 0xFE>>\n"
" ```\n"
).
-spec capture(parser(any())) -> parser(bitstring()).
capture(Parser) ->
{parser,
fun(State) ->
require_aligned(
State,
fun() -> case (erlang:element(2, Parser))(State) of
{continue, _, State1, Consumed} ->
gleam@bool:guard(
erlang:element(4, State1) /= 0,
{stop,
{bitty_error,
{location,
erlang:element(3, State1),
erlang:element(4, State1)},
[<<"byte alignment after capture"/utf8>>],
[],
none},
Consumed,
erlang:element(5, State1)},
fun() ->
Byte_count = erlang:element(3, State1) - erlang:element(
3,
State
),
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
erlang:element(3, State),
Byte_count
) of
{ok, Captured} ->
{continue,
Captured,
State1,
Consumed};
_ ->
stop_expected(
State,
<<"valid slice"/utf8>>
)
end
end
);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end
)
end}.
-file("src/bitty.gleam", 1029).
?DOC(
" Parse a byte count, then run a parser within a window of that many\n"
" bytes. Dynamic version of `within_bytes`.\n"
"\n"
" ```gleam\n"
" let parser = bitty.length_within(num.u8(), run: bytes.rest())\n"
" let assert Ok(data) = bitty.run(parser, on: <<3, 0xAA, 0xBB, 0xCC>>)\n"
" assert data == <<0xAA, 0xBB, 0xCC>>\n"
" ```\n"
).
-spec length_within(parser(integer()), parser(PHG)) -> parser(PHG).
length_within(Count, Parser) ->
_pipe = Count,
then(_pipe, fun(N) -> within_bytes(N, Parser) end).
-file("src/bitty.gleam", 1165).
-spec remaining_bits_of_byte(state()) -> bitstring().
remaining_bits_of_byte(State) ->
Available = 8 - erlang:element(4, State),
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
erlang:element(3, State),
1
) of
{ok, <<Byte>>} ->
Mask = erlang:'bsl'(1, Available) - 1,
Bits_val = erlang:'band'(Byte, Mask),
<<Bits_val:(lists:max([(Available), 0]))>>;
_ ->
<<>>
end.
-file("src/bitty.gleam", 1177).
-spec remaining_slice(state()) -> bitstring().
remaining_slice(State) ->
case erlang:element(4, State) /= 0 of
true ->
Partial = remaining_bits_of_byte(State),
Full_offset = erlang:element(3, State) + 1,
Remaining = erlang:byte_size(erlang:element(2, State)) - Full_offset,
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
Full_offset,
Remaining
) of
{ok, Rest} ->
<<Partial/bitstring, Rest/bitstring>>;
_ ->
Partial
end;
false ->
Remaining@1 = erlang:byte_size(erlang:element(2, State)) - erlang:element(
3,
State
),
case gleam_stdlib:bit_array_slice(
erlang:element(2, State),
erlang:element(3, State),
Remaining@1
) of
{ok, Rest@1} ->
Rest@1;
_ ->
<<>>
end
end.
-file("src/bitty.gleam", 1198).
-spec new_state(bitstring()) -> state().
new_state(Input) ->
{state, Input, 0, 0, false}.
-file("src/bitty.gleam", 149).
-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", 128).
?DOC(
" Run a parser on the given input, returning the parsed value and any\n"
" unconsumed input. Does not require all input to be consumed.\n"
" The returned `BitArray` may not be byte-aligned when bit-level parsers\n"
" are used.\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", 140).
?DOC(
" Like `run_partial`, but also returns the `Location` where the parser\n"
" stopped. Useful for incremental or streaming parsing.\n"
" The returned `BitArray` may not be byte-aligned when bit-level parsers\n"
" are used.\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
).