-module(sixtytwo). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sixtytwo.gleam"). -export([encode/1, decode/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( " Base62 encoding and decoding for Gleam, targeting both Erlang and JavaScript.\n" "\n" " Uses the standard `0-9A-Za-z` alphabet with Bitcoin/base-x style leading\n" " zero preservation, compatible with `cryptocoinjs/base-x`.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " sixtytwo.encode(<<\"hello\":utf8>>)\n" " // -> \"7tQLFHz\"\n" "\n" " sixtytwo.decode(\"7tQLFHz\")\n" " // -> Ok(<<\"hello\":utf8>>)\n" " ```\n" ). -file("src/sixtytwo.gleam", 62). -spec count_leading_zeros(bitstring(), integer()) -> integer(). count_leading_zeros(Input, Count) -> case Input of <<0, Rest/binary>> -> count_leading_zeros(Rest, Count + 1); _ -> Count end. -file("src/sixtytwo.gleam", 69). -spec count_leading_zero_values(list(integer())) -> integer(). count_leading_zero_values(Values) -> case Values of [0 | Rest] -> 1 + count_leading_zero_values(Rest); _ -> 0 end. -file("src/sixtytwo.gleam", 87). -spec codepoint_to_index(integer()) -> {ok, integer()} | {error, nil}. codepoint_to_index(Code) -> case Code of C when (C >= 48) andalso (C =< 57) -> {ok, C - 48}; C@1 when (C@1 >= 65) andalso (C@1 =< 90) -> {ok, (C@1 - 65) + 10}; C@2 when (C@2 >= 97) andalso (C@2 =< 122) -> {ok, (C@2 - 97) + 36}; _ -> {error, nil} end. -file("src/sixtytwo.gleam", 80). -spec char_to_value(binary()) -> {ok, integer()} | {error, nil}. char_to_value(Char) -> case gleam@string:to_utf_codepoints(Char) of [Cp] -> codepoint_to_index(gleam_stdlib:identity(Cp)); _ -> {error, nil} end. -file("src/sixtytwo.gleam", 123). -spec do_multiply_and_add(list(integer()), integer(), integer(), integer()) -> {list(integer()), integer()}. do_multiply_and_add(Digits, Multiplier, Addend, Digit_base) -> case Digits of [] -> {[], Addend}; [Digit | Rest] -> {Processed_rest, Carry} = do_multiply_and_add( Rest, Multiplier, Addend, Digit_base ), Value = (Digit * Multiplier) + Carry, New_digit = case Digit_base of 0 -> 0; Gleam@denominator -> Value rem Gleam@denominator end, New_carry = case Digit_base of 0 -> 0; Gleam@denominator@1 -> Value div Gleam@denominator@1 end, {[New_digit | Processed_rest], New_carry} end. -file("src/sixtytwo.gleam", 142). -spec emit_carry(integer(), integer(), list(integer())) -> list(integer()). emit_carry(Carry, Digit_base, Acc) -> case Carry of 0 -> Acc; _ -> Digit = case Digit_base of 0 -> 0; Gleam@denominator -> Carry rem Gleam@denominator end, Remaining = case Digit_base of 0 -> 0; Gleam@denominator@1 -> Carry div Gleam@denominator@1 end, emit_carry(Remaining, Digit_base, [Digit | Acc]) end. -file("src/sixtytwo.gleam", 112). -spec multiply_and_add(list(integer()), integer(), integer(), integer()) -> list(integer()). multiply_and_add(Digits, Multiplier, Addend, Digit_base) -> {Result, Carry} = do_multiply_and_add( Digits, Multiplier, Addend, Digit_base ), emit_carry(Carry, Digit_base, Result). -file("src/sixtytwo.gleam", 76). -spec index_to_char(integer()) -> binary(). index_to_char(Index) -> gleam@string:slice( <<"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"/utf8>>, Index, 1 ). -file("src/sixtytwo.gleam", 96). -spec bytes_to_base_digits(bitstring(), list(integer())) -> list(integer()). bytes_to_base_digits(Input, Digits) -> case Input of <> -> New_digits = multiply_and_add(Digits, 256, Byte, 62), bytes_to_base_digits(Rest, New_digits); _ -> Digits end. -file("src/sixtytwo.gleam", 28). ?DOC( " Encode a bit array using base62. The input must be byte-aligned.\n" " Leading zero bytes are preserved as `0` characters in the output.\n" ). -spec encode(bitstring()) -> binary(). encode(Input) -> gleam@bool:guard( erlang:byte_size(Input) =:= 0, <<""/utf8>>, fun() -> Leading_zeros = count_leading_zeros(Input, 0), Prefix = gleam@string:repeat(<<"0"/utf8>>, Leading_zeros), Digits = bytes_to_base_digits(Input, []), case Digits of [] -> Prefix; _ -> Encoded = begin _pipe = gleam@list:map(Digits, fun index_to_char/1), erlang:list_to_binary(_pipe) end, <> end end ). -file("src/sixtytwo.gleam", 106). -spec base_digits_to_bytes(list(integer())) -> list(integer()). base_digits_to_bytes(Digits) -> gleam@list:fold( Digits, [], fun(Bytes, Digit) -> multiply_and_add(Bytes, 62, Digit, 256) end ). -file("src/sixtytwo.gleam", 46). ?DOC(" Decode a base62 string. Returns `Error(Nil)` on invalid input.\n"). -spec decode(binary()) -> {ok, bitstring()} | {error, nil}. decode(Input) -> gleam@bool:guard( Input =:= <<""/utf8>>, {ok, <<>>}, fun() -> Chars = gleam@string:to_graphemes(Input), gleam@result:'try'( gleam@list:try_map(Chars, fun char_to_value/1), fun(Values) -> Leading_zeros = count_leading_zero_values(Values), Bytes = base_digits_to_bytes(Values), Decoded = begin _pipe = gleam@list:repeat(0, Leading_zeros), _pipe@1 = lists:append(_pipe, Bytes), _pipe@2 = gleam@list:map(_pipe@1, fun(B) -> <> end), gleam_stdlib:bit_array_concat(_pipe@2) end, {ok, Decoded} end ) end ).