#![allow(clippy::upper_case_acronyms)]
use super::*;
use crate::impl_ule_from_array;
use core::cmp::Ordering;
use core::convert::TryFrom;
#[repr(transparent)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct CharULE([u8; 3]);
impl CharULE {
#[inline]
pub const fn from_aligned(c: char) -> Self {
let [u0, u1, u2, _u3] = (c as u32).to_le_bytes();
Self([u0, u1, u2])
}
#[inline]
pub fn to_char(self) -> char {
let [b0, b1, b2] = self.0;
unsafe { char::from_u32_unchecked(u32::from_le_bytes([b0, b1, b2, 0])) }
}
impl_ule_from_array!(char, CharULE, Self([0; 3]));
}
unsafe impl ULE for CharULE {
#[inline]
fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
if bytes.len() % 3 != 0 {
return Err(UleError::length::<Self>(bytes.len()));
}
for chunk in bytes.chunks_exact(3) {
#[expect(clippy::indexing_slicing)]
let u = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], 0]);
char::try_from(u).map_err(|_| UleError::parse::<Self>())?;
}
Ok(())
}
}
impl AsULE for char {
type ULE = CharULE;
#[inline]
fn to_unaligned(self) -> Self::ULE {
CharULE::from_aligned(self)
}
#[inline]
fn from_unaligned(unaligned: Self::ULE) -> Self {
unaligned.to_char()
}
}
impl PartialOrd for CharULE {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CharULE {
fn cmp(&self, other: &Self) -> Ordering {
char::from_unaligned(*self).cmp(&char::from_unaligned(*other))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_from_array() {
const CHARS: [char; 2] = ['a', '🙃'];
const CHARS_ULE: [CharULE; 2] = CharULE::from_array(CHARS);
assert_eq!(
CharULE::slice_as_bytes(&CHARS_ULE),
&[0x61, 0x00, 0x00, 0x43, 0xF6, 0x01]
);
}
#[test]
fn test_from_array_zst() {
const CHARS: [char; 0] = [];
const CHARS_ULE: [CharULE; 0] = CharULE::from_array(CHARS);
let bytes = CharULE::slice_as_bytes(&CHARS_ULE);
let empty: &[u8] = &[];
assert_eq!(bytes, empty);
}
#[test]
fn test_parse() {
let chars = ['w', 'ω', '文', '𑄃', '🙃'];
let char_ules: Vec<CharULE> = chars.iter().copied().map(char::to_unaligned).collect();
let char_bytes: &[u8] = CharULE::slice_as_bytes(&char_ules);
let parsed_ules: &[CharULE] = CharULE::parse_bytes_to_slice(char_bytes).unwrap();
assert_eq!(char_ules, parsed_ules);
let parsed_chars: Vec<char> = parsed_ules
.iter()
.copied()
.map(char::from_unaligned)
.collect();
assert_eq!(&chars, parsed_chars.as_slice());
assert_eq!(
&[119, 0, 0, 201, 3, 0, 135, 101, 0, 3, 17, 1, 67, 246, 1],
char_bytes
);
}
#[test]
fn test_failures() {
let u32s = [119, 0xD800, 120];
let u32_ules: Vec<RawBytesULE<4>> = u32s
.iter()
.copied()
.map(<u32 as AsULE>::to_unaligned)
.collect();
let u32_bytes: &[u8] = RawBytesULE::<4>::slice_as_bytes(&u32_ules);
let parsed_ules_result = CharULE::parse_bytes_to_slice(u32_bytes);
assert!(parsed_ules_result.is_err());
let u32s = [0x20FFFF];
let u32_ules: Vec<RawBytesULE<4>> = u32s
.iter()
.copied()
.map(<u32 as AsULE>::to_unaligned)
.collect();
let u32_bytes: &[u8] = RawBytesULE::<4>::slice_as_bytes(&u32_ules);
let parsed_ules_result = CharULE::parse_bytes_to_slice(u32_bytes);
assert!(parsed_ules_result.is_err());
}
}