-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, <>, 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, <>, 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, <>, 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, <>, 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, <>, New_state, Consumed} -> Ordered = case Endian of big_endian -> <>; little_endian -> <> end, case Ordered of <> -> {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, <>, New_state, Consumed} -> Ordered = case Endian of big_endian -> <>; little_endian -> <> end, case Ordered of <> -> {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_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).