Current section
Files
Jump to
Current section
Files
src/bitty@num.erl
-module(bitty@num).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/bitty/num.gleam").
-export([u8/0, i8/0, u16/1, u32/1, i16/1, i32/1, u64/1, i64/1, f32/1, f64/1, var_u32/0, var_u64/0, uint_bytes/1, int_bytes_twos_complement/1]).
-export_type([endian/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(
" Numeric parsers for fixed-width integers, floats, and variable-length\n"
" encodings. Includes single-byte (`u8`, `i8`) and multi-byte types with\n"
" endianness control.\n"
).
-type endian() :: big_endian | little_endian.
-file("src/bitty/num.gleam", 18).
?DOC(" Parse a single byte as an unsigned integer (0–255).\n").
-spec u8() -> bitty:parser(integer()).
u8() ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, 1) of
{continue, <<Byte>>, New_state, Consumed} ->
{continue, Byte, New_state, Consumed};
{continue, _, New_state@1, _} ->
bitty:stop_expected(New_state@1, <<"a byte"/utf8>>);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end).
-file("src/bitty/num.gleam", 32).
?DOC(" Parse a single byte as a signed integer (-128–127).\n").
-spec i8() -> bitty:parser(integer()).
i8() ->
_pipe = u8(),
bitty:map(_pipe, fun(Unsigned) -> case Unsigned >= 128 of
true ->
Unsigned - 256;
false ->
Unsigned
end end).
-file("src/bitty/num.gleam", 49).
?DOC(
" Parse a 16-bit unsigned integer with the given endianness.\n"
"\n"
" ```gleam\n"
" let assert Ok(val) =\n"
" bitty.run(num.u16(num.BigEndian), on: <<0x01, 0x00>>)\n"
" assert val == 256\n"
" ```\n"
).
-spec u16(endian()) -> bitty:parser(integer()).
u16(Endian) ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, 2) of
{continue, <<B0, B1>>, New_state, Consumed} ->
Value = case Endian of
big_endian ->
(B0 * 256) + B1;
little_endian ->
(B1 * 256) + B0
end,
{continue, Value, New_state, Consumed};
{continue, _, _, _} ->
bitty:stop_expected(State, <<"2 bytes"/utf8>>);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end).
-file("src/bitty/num.gleam", 67).
?DOC(" Parse a 32-bit unsigned integer with the given endianness.\n").
-spec u32(endian()) -> bitty:parser(integer()).
u32(Endian) ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, 4) of
{continue, <<B0, B1, B2, B3>>, New_state, Consumed} ->
Value = case Endian of
big_endian ->
(((B0 * 16777216) + (B1 * 65536)) + (B2 * 256)) + B3;
little_endian ->
(((B3 * 16777216) + (B2 * 65536)) + (B1 * 256)) + B0
end,
{continue, Value, New_state, Consumed};
{continue, _, _, _} ->
bitty:stop_expected(State, <<"4 bytes"/utf8>>);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end).
-file("src/bitty/num.gleam", 85).
?DOC(" Parse a 16-bit signed integer (two's complement) with the given endianness.\n").
-spec i16(endian()) -> bitty:parser(integer()).
i16(Endian) ->
_pipe = u16(Endian),
bitty:map(_pipe, fun(Unsigned) -> case Unsigned >= 32768 of
true ->
Unsigned - 65536;
false ->
Unsigned
end end).
-file("src/bitty/num.gleam", 96).
?DOC(" Parse a 32-bit signed integer (two's complement) with the given endianness.\n").
-spec i32(endian()) -> bitty:parser(integer()).
i32(Endian) ->
_pipe = u32(Endian),
bitty:map(_pipe, fun(Unsigned) -> case Unsigned >= 2147483648 of
true ->
Unsigned - 4294967296;
false ->
Unsigned
end end).
-file("src/bitty/num.gleam", 109).
?DOC(
" Parse a 64-bit unsigned integer with the given endianness.\n"
" On the JavaScript target, values above 2^53 - 1 (9,007,199,254,740,991)\n"
" may lose precision due to IEEE 754 double-precision limitations.\n"
).
-spec u64(endian()) -> bitty:parser(integer()).
u64(Endian) ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, 8) of
{continue,
<<B0, B1, B2, B3, B4, B5, B6, B7>>,
New_state,
Consumed} ->
{Hi, Lo} = case Endian of
big_endian ->
{(((B0 * 16777216) + (B1 * 65536)) + (B2 * 256)) + B3,
(((B4 * 16777216) + (B5 * 65536)) + (B6 * 256))
+ B7};
little_endian ->
{(((B7 * 16777216) + (B6 * 65536)) + (B5 * 256)) + B4,
(((B3 * 16777216) + (B2 * 65536)) + (B1 * 256))
+ B0}
end,
{continue, (Hi * 4294967296) + Lo, New_state, Consumed};
{continue, _, _, _} ->
bitty:stop_expected(State, <<"8 bytes"/utf8>>);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end).
-file("src/bitty/num.gleam", 135).
?DOC(
" Parse a 64-bit signed integer (two's complement) with the given endianness.\n"
" On the JavaScript target, values outside the safe integer range\n"
" (-2^53 + 1 to 2^53 - 1) may lose precision.\n"
).
-spec i64(endian()) -> bitty:parser(integer()).
i64(Endian) ->
_pipe = u64(Endian),
bitty:map(_pipe, fun(Unsigned) -> case Unsigned >= 9223372036854775808 of
true ->
Unsigned - 18446744073709551616;
false ->
Unsigned
end end).
-file("src/bitty/num.gleam", 146).
?DOC(" Parse a 32-bit IEEE 754 float with the given endianness.\n").
-spec f32(endian()) -> bitty:parser(float()).
f32(Endian) ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, 4) of
{continue, <<B0, B1, B2, B3>>, New_state, Consumed} ->
Ordered = case Endian of
big_endian ->
<<B0, B1, B2, B3>>;
little_endian ->
<<B3, B2, B1, B0>>
end,
case Ordered of
<<Value:32/float>> ->
{continue, Value, New_state, Consumed};
_ ->
bitty:stop_expected(State, <<"valid f32"/utf8>>)
end;
{continue, _, _, _} ->
bitty:stop_expected(State, <<"4 bytes"/utf8>>);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end).
-file("src/bitty/num.gleam", 167).
?DOC(" Parse a 64-bit IEEE 754 double with the given endianness.\n").
-spec f64(endian()) -> bitty:parser(float()).
f64(Endian) ->
bitty:make_parser(fun(State) -> case bitty:read_n_bytes(State, 8) of
{continue,
<<B0, B1, B2, B3, B4, B5, B6, B7>>,
New_state,
Consumed} ->
Ordered = case Endian of
big_endian ->
<<B0, B1, B2, B3, B4, B5, B6, B7>>;
little_endian ->
<<B7, B6, B5, B4, B3, B2, B1, B0>>
end,
case Ordered of
<<Value/float>> ->
{continue, Value, New_state, Consumed};
_ ->
bitty:stop_expected(State, <<"valid f64"/utf8>>)
end;
{continue, _, _, _} ->
bitty:stop_expected(State, <<"8 bytes"/utf8>>);
{stop, Error, Consumed@1, Committed} ->
{stop, Error, Consumed@1, Committed}
end end).
-file("src/bitty/num.gleam", 255).
-spec varint_overflow(bitty:state(), binary()) -> bitty:step(any()).
varint_overflow(State, Expected) ->
{stop,
{bitty_error,
{location, erlang:element(3, State), erlang:element(4, State)},
[Expected],
[],
none},
true,
erlang:element(5, State)}.
-file("src/bitty/num.gleam", 206).
-spec varint_loop(
bitty:state(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
binary()
) -> bitty:step(integer()).
varint_loop(
State,
Acc,
Shift,
Bytes_read,
Max_bytes,
Last_byte_index,
Last_byte_max,
Label
) ->
gleam@bool:guard(
Bytes_read >= Max_bytes,
varint_overflow(
State,
<<<<"varint within "/utf8,
(erlang:integer_to_binary(Max_bytes))/binary>>/binary,
" bytes"/utf8>>
),
fun() -> case bitty:read_n_bytes(State, 1) of
{continue, <<Byte>>, Byte_state, _} ->
Value = erlang:'band'(Byte, 16#7F),
gleam@bool:guard(
(Bytes_read =:= Last_byte_index) andalso (Value > Last_byte_max),
varint_overflow(
Byte_state,
<<<<"varint within "/utf8, Label/binary>>/binary,
" range"/utf8>>
),
fun() ->
New_acc = erlang:'bor'(
Acc,
erlang:'bsl'(Value, Shift)
),
case erlang:'band'(Byte, 16#80) /= 0 of
true ->
varint_loop(
Byte_state,
New_acc,
Shift + 7,
Bytes_read + 1,
Max_bytes,
Last_byte_index,
Last_byte_max,
Label
);
false ->
{continue, New_acc, Byte_state, true}
end
end
);
{continue, _, _, _} ->
bitty:stop_expected(State, <<"varint byte"/utf8>>);
{stop, Error, Consumed, Committed} ->
{stop, Error, Consumed, Committed}
end end
).
-file("src/bitty/num.gleam", 194).
?DOC(
" Parse an LEB128-encoded unsigned 32-bit variable-length integer.\n"
" Consumes at most 5 bytes.\n"
"\n"
" ```gleam\n"
" let assert Ok(val) = bitty.run(num.var_u32(), on: <<0xAC, 0x02>>)\n"
" assert val == 300\n"
" ```\n"
).
-spec var_u32() -> bitty:parser(integer()).
var_u32() ->
bitty:make_parser(
fun(_capture) ->
varint_loop(_capture, 0, 0, 0, 5, 4, 16#0F, <<"u32"/utf8>>)
end
).
-file("src/bitty/num.gleam", 202).
?DOC(
" Parse an LEB128-encoded unsigned 64-bit variable-length integer.\n"
" Consumes at most 10 bytes.\n"
" On the JavaScript target, values above 2^53 - 1 (9,007,199,254,740,991)\n"
" may lose precision due to IEEE 754 double-precision limitations.\n"
).
-spec var_u64() -> bitty:parser(integer()).
var_u64() ->
bitty:make_parser(
fun(_capture) ->
varint_loop(_capture, 0, 0, 0, 10, 9, 16#01, <<"u64"/utf8>>)
end
).
-file("src/bitty/num.gleam", 272).
?DOC(
" Read `count` bytes as a raw `BitArray` representing an unsigned integer.\n"
" Unlike `bytes.take`, this is intended for numeric byte sequences\n"
" (e.g. ASN.1 DER integer encodings) where the bytes represent a single\n"
" big-endian value.\n"
).
-spec uint_bytes(integer()) -> bitty:parser(bitstring()).
uint_bytes(Count) ->
bitty:make_parser(fun(_capture) -> bitty:read_n_bytes(_capture, Count) end).
-file("src/bitty/num.gleam", 285).
-spec strip_leading_zeros(bitstring()) -> bitstring().
strip_leading_zeros(Bytes) ->
case Bytes of
<<16#00, Next, _/bitstring>> when Next >= 16#80 ->
Bytes;
<<16#00, _, _/bitstring>> ->
case gleam_stdlib:bit_array_slice(
Bytes,
1,
erlang:byte_size(Bytes) - 1
) of
{ok, Rest} ->
strip_leading_zeros(Rest);
_ ->
Bytes
end;
_ ->
Bytes
end.
-file("src/bitty/num.gleam", 280).
?DOC(
" Read `count` bytes as a two's complement signed integer `BitArray`,\n"
" stripping redundant leading zero bytes while preserving the sign bit.\n"
" Returns the normalized raw bytes, not a decoded `Int`.\n"
" Useful for ASN.1 DER integer encoding.\n"
).
-spec int_bytes_twos_complement(integer()) -> bitty:parser(bitstring()).
int_bytes_twos_complement(Count) ->
_pipe = uint_bytes(Count),
bitty:map(_pipe, fun strip_leading_zeros/1).