1#![allow(clippy::upper_case_acronyms)]
6use super::*;
9use crate::impl_ule_from_array;
10use core::cmp::Ordering;
11use core::convert::TryFrom;
12
13#[repr(transparent)]
41#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
42pub struct CharULE([u8; 3]);
43
44impl CharULE {
45 #[inline]
50 pub const fn from_aligned(c: char) -> Self {
51 let [u0, u1, u2, _u3] = (c as u32).to_le_bytes();
52 Self([u0, u1, u2])
53 }
54
55 #[inline]
60 pub fn to_char(self) -> char {
61 let [b0, b1, b2] = self.0;
62 unsafe { char::from_u32_unchecked(u32::from_le_bytes([b0, b1, b2, 0])) }
64 }
65
66 impl_ule_from_array!(char, CharULE, Self([0; 3]));
67}
68
69unsafe impl ULE for CharULE {
79 #[inline]
80 fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
81 if bytes.len() % 3 != 0 {
82 return Err(UleError::length::<Self>(bytes.len()));
83 }
84 for chunk in bytes.chunks_exact(3) {
86 #[allow(clippy::indexing_slicing)]
88 let u = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], 0]);
90 char::try_from(u).map_err(|_| UleError::parse::<Self>())?;
91 }
92 Ok(())
93 }
94}
95
96impl AsULE for char {
97 type ULE = CharULE;
98
99 #[inline]
100 fn to_unaligned(self) -> Self::ULE {
101 CharULE::from_aligned(self)
102 }
103
104 #[inline]
105 fn from_unaligned(unaligned: Self::ULE) -> Self {
106 unaligned.to_char()
107 }
108}
109
110impl PartialOrd for CharULE {
111 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112 Some(self.cmp(other))
113 }
114}
115
116impl Ord for CharULE {
117 fn cmp(&self, other: &Self) -> Ordering {
118 char::from_unaligned(*self).cmp(&char::from_unaligned(*other))
119 }
120}
121
122#[cfg(test)]
123mod test {
124 use super::*;
125
126 #[test]
127 fn test_from_array() {
128 const CHARS: [char; 2] = ['a', '🙃'];
129 const CHARS_ULE: [CharULE; 2] = CharULE::from_array(CHARS);
130 assert_eq!(
131 CharULE::slice_as_bytes(&CHARS_ULE),
132 &[0x61, 0x00, 0x00, 0x43, 0xF6, 0x01]
133 );
134 }
135
136 #[test]
137 fn test_from_array_zst() {
138 const CHARS: [char; 0] = [];
139 const CHARS_ULE: [CharULE; 0] = CharULE::from_array(CHARS);
140 let bytes = CharULE::slice_as_bytes(&CHARS_ULE);
141 let empty: &[u8] = &[];
142 assert_eq!(bytes, empty);
143 }
144
145 #[test]
146 fn test_parse() {
147 let chars = ['w', 'ω', '文', '𑄃', '🙃'];
149 let char_ules: Vec<CharULE> = chars.iter().copied().map(char::to_unaligned).collect();
150 let char_bytes: &[u8] = CharULE::slice_as_bytes(&char_ules);
151
152 let parsed_ules: &[CharULE] = CharULE::parse_bytes_to_slice(char_bytes).unwrap();
154 assert_eq!(char_ules, parsed_ules);
155 let parsed_chars: Vec<char> = parsed_ules
156 .iter()
157 .copied()
158 .map(char::from_unaligned)
159 .collect();
160 assert_eq!(&chars, parsed_chars.as_slice());
161
162 assert_eq!(
164 &[119, 0, 0, 201, 3, 0, 135, 101, 0, 3, 17, 1, 67, 246, 1],
165 char_bytes
166 );
167 }
168
169 #[test]
170 fn test_failures() {
171 let u32s = [119, 0xD800, 120];
173 let u32_ules: Vec<RawBytesULE<4>> = u32s
174 .iter()
175 .copied()
176 .map(<u32 as AsULE>::to_unaligned)
177 .collect();
178 let u32_bytes: &[u8] = RawBytesULE::<4>::slice_as_bytes(&u32_ules);
179 let parsed_ules_result = CharULE::parse_bytes_to_slice(u32_bytes);
180 assert!(parsed_ules_result.is_err());
181
182 let u32s = [0x20FFFF];
184 let u32_ules: Vec<RawBytesULE<4>> = u32s
185 .iter()
186 .copied()
187 .map(<u32 as AsULE>::to_unaligned)
188 .collect();
189 let u32_bytes: &[u8] = RawBytesULE::<4>::slice_as_bytes(&u32_ules);
190 let parsed_ules_result = CharULE::parse_bytes_to_slice(u32_bytes);
191 assert!(parsed_ules_result.is_err());
192 }
193}