use core::{error, fmt, num::NonZeroU128, slice, str};
const _: () = {
assert!(
size_of::<Name>() == size_of::<Option<Name>>(),
"`Name` and `Option<Name>` must have the same size",
);
assert!(
align_of::<Name>() == align_of::<u64>(),
"`Name` and `u64` must have the same alignment",
);
};
#[repr(C, packed)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct InnerU128(NonZeroU128);
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Name(InnerU128, [u64; 0]);
impl Name {
pub const MAXLEN: usize = 24;
#[inline]
pub const fn encode(s: &[u8]) -> Result<Self, Error> {
if s.is_empty() {
return Err(Error::Empty);
}
if s.len() > Self::MAXLEN {
return Err(Error::TooLong);
}
if let [b'_', ..] = s {
return Err(Error::LeadingUnderscore);
}
let mut mul = 1;
let mut sum = 0;
let mut idx = s.len() - 1;
loop {
let u = match s[idx] {
b'_' => 0,
n @ b'a'..=b'z' => n - b'a' + 1,
n @ b'0'..=b'9' => n - b'0' + 27,
_ => return Err(Error::InvalidChar),
};
sum += u as u128 * mul;
mul *= 37;
if idx == 0 {
break;
}
idx -= 1;
}
let num = unsafe { NonZeroU128::new_unchecked(sum) };
Ok(Self(InnerU128(num), []))
}
#[inline]
pub const fn encode_char(c: char) -> Result<Self, Error> {
match (c as u32).to_le_bytes() {
[v, 0, 0, 0] => Self::encode(slice::from_ref(&v)),
_ => Err(Error::InvalidChar),
}
}
#[inline]
pub const fn decode(self) -> DecodedName {
let mut num = self.0.0.get();
let mut buf = [0; Self::MAXLEN];
let mut idx = buf.len() - 1;
loop {
buf[idx] = match (num % 37) as u8 {
0 => b'_',
n @ 1..27 => n + b'a' - 1,
n @ 27..37 => n + b'0' - 27,
_ => unreachable!(),
};
num /= 37;
if num == 0 || idx == 0 {
break;
}
idx -= 1;
}
DecodedName { buf, idx }
}
#[inline]
pub const fn len(self) -> usize {
u128::ilog(self.0.0.get(), 37) as usize + 1
}
#[inline]
pub const fn is_empty(self) -> bool {
false
}
}
impl fmt::Display for Name {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.decode().as_str())
}
}
impl fmt::Debug for Name {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.decode().as_str())
}
}
impl TryFrom<&[u8]> for Name {
type Error = Error;
#[inline]
fn try_from(s: &[u8]) -> Result<Self, Self::Error> {
Self::encode(s)
}
}
impl TryFrom<&str> for Name {
type Error = Error;
#[inline]
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::encode(s.as_bytes())
}
}
#[derive(Clone, Copy, Debug)]
pub struct DecodedName {
buf: [u8; Name::MAXLEN],
idx: usize,
}
impl DecodedName {
#[inline]
pub const fn as_slice(&self) -> &[u8] {
let len = self.buf.len() - self.idx;
let ptr = unsafe { self.buf.as_ptr().add(self.idx) };
unsafe { slice::from_raw_parts(ptr, len) }
}
#[inline]
pub const fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.as_slice()) }
}
}
impl AsRef<[u8]> for DecodedName {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl AsRef<str> for DecodedName {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
Empty,
TooLong,
LeadingUnderscore,
InvalidChar,
}
impl Error {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Empty => "the name is empty",
Self::TooLong => "the name is too long",
Self::LeadingUnderscore => "the name starts with `_`",
Self::InvalidChar => "the name contains an invalid char",
}
}
}
impl fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl error::Error for Error {}
#[cfg(feature = "std")]
impl From<Error> for std::io::Error {
#[inline]
fn from(e: Error) -> Self {
Self::new(std::io::ErrorKind::InvalidData, e.as_str())
}
}
#[macro_export]
macro_rules! name {
($s:literal) => {
const {
let s: &str = $s;
match $crate::Name::encode(s.as_bytes()) {
::core::result::Result::Ok(name) => name,
::core::result::Result::Err(e) => ::core::panic!("{}", e.as_str()),
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn code() -> Result<(), Error> {
let s = b"hello";
let name = Name::encode(s)?;
assert_eq!(name.decode().as_slice(), s);
assert_eq!(name.len(), 5);
Ok(())
}
#[test]
fn code_const() {
let string = const {
let name = name!("hellowo");
name.decode()
};
assert_eq!(string.as_str(), "hellowo");
}
#[test]
fn cases() {
let tests = [
"a",
"z",
"0",
"9",
"a_",
"99",
"999",
"999_",
"hi",
"some_key",
"lol________",
"0123456789",
"abcdefxyz",
"a1b2c3d_e_f9",
"small_strings_rule",
"123__qwe__456_xyz__098__",
];
for test in tests {
let name = Name::try_from(test).expect("make name");
assert_eq!(name.decode().as_str(), test);
assert_eq!(name.len(), test.len());
}
}
#[test]
fn code_long() -> Result<(), Error> {
let s = b"999999999999999999999999";
let name = Name::encode(s)?;
assert_eq!(name.decode().as_slice(), s);
assert_eq!(name.len(), 24);
Ok(())
}
#[test]
fn encode_char() -> Result<(), Error> {
let name = Name::encode_char('a')?;
assert_eq!(name.decode().as_str(), "a");
assert_eq!(name.len(), 1);
Ok(())
}
#[test]
fn encode_invalid_char() {
assert_eq!(Name::encode_char('ю'), Err(Error::InvalidChar));
}
#[test]
fn zero_len() {
let empty = b"";
assert_eq!(Name::encode(empty), Err(Error::Empty));
}
#[test]
fn too_long() {
let long = b"9999999999999999999999999";
assert_eq!(Name::encode(long), Err(Error::TooLong));
}
#[test]
fn leading_underscore() {
let underscored = b"_hello";
assert_eq!(Name::encode(underscored), Err(Error::LeadingUnderscore));
}
#[test]
fn invalid_char() {
let s = b"HELLO";
assert_eq!(Name::encode(s), Err(Error::InvalidChar));
}
}