Packages
base_emoji
1.0.0
Encoding to emoji designed to produce visually easily distinguished strings
Current section
Files
Jump to
Current section
Files
src/base_emoji.gleam
import gblake3
import gleam/bit_array
import gleam/list
import internal/map.{type Map}
import internal/v1.{v1}
pub type MapVersion {
V1
}
fn version_to_map(v: MapVersion) -> Map {
case v {
V1 -> v1
}
}
const latest: MapVersion = V1
/// Encodes the given bit array with the latest emoji map
pub fn encode(bits b: BitArray) -> String {
b |> encode_with_version(latest)
}
/// Econdes the given bit array with the requested emoji map
/// Unless you have need of a constant display, [encode](#encode) should
/// be preferred to get the latest map, which attempts to maximize the
/// visual difference between two arrays
pub fn encode_with_version(bits b: BitArray, version v: MapVersion) -> String {
let map = v |> version_to_map
let byte_size = b |> bit_array.byte_size
b |> gblake3.hash_to_length(byte_size) |> encode_inner(map, "")
}
fn encode_inner(b: BitArray, map: Map, state: String) -> String {
case b |> bit_array.bit_size {
0 -> state
_ -> {
let assert Ok(#(int, rest)) = b |> take_int
let assert Ok(next) = map |> list.key_find(int)
encode_inner(rest, map, state <> next)
}
}
}
/// Takes a byte int from the front of the given BitArray, converts it to
/// an Int guaranteed to be 0-255. The remaining bits after this operation are also returned.
/// If the BitArray is empty, Error(nil) is returned.
fn take_int(b: BitArray) -> Result(#(Int, BitArray), Nil) {
case b |> take_byte {
Ok(#(first, rest)) -> {
case first |> u8_from_bits {
Ok(int) -> Ok(#(int, rest))
Error(_) -> Error(Nil)
}
}
Error(_) -> Error(Nil)
}
}
/// Takes a byte from the front of the given array. If the bit array is not empty
/// returns Ok tuple with the first item being a bit array filled with the taken bits
/// and padded to length 8. The second item is the remaining bits. If the input is empty,
/// Error(Nil) is returned.
fn take_byte(b: BitArray) -> Result(#(BitArray, BitArray), Nil) {
case b |> bit_array.bit_size {
0 -> Error(Nil)
// one byte or less left
c if c <= 8 -> {
let assert Ok(taken) =
b |> bit_array.pad_to_bytes |> bit_array.slice(0, 1)
Ok(#(taken |> bit_array.pad_to_bytes, <<>>))
}
// More than a byte left
c -> {
let assert Ok(taken) = b |> bit_array.slice(0, 1)
let assert Ok(remaining) = b |> bit_array.slice(1, c / 8 - 1)
Ok(#(taken, remaining))
}
}
}
/// Converts a byte-padded BitArray to a u8 integer.
/// Errors if the BitArray is not 8 bits long
@internal
pub fn u8_from_bits(b: BitArray) -> Result(Int, Nil) {
case b |> bit_array.bit_size {
c if c == 8 -> Ok(b |> u8_from_bits_internal)
_ -> Error(Nil)
}
}
@external(erlang, "Elixir.BaseEmoji", "u8_from_bits")
@external(javascript, "./base_emoji_ffi.mjs", "u8_from_bits")
fn u8_from_bits_internal(b: BitArray) -> Int