1#![cfg_attr(not(feature = "std"), no_std)]
2#![allow(non_camel_case_types)]
3
4extern crate four_char_code_macros_impl;
5extern crate proc_macro_hack;
6
7use core::{
8 cmp::Ordering,
9 fmt::{self, Write},
10};
11
12#[cfg(feature = "std")]
13use std::string::{String, ToString};
14
15#[derive(Debug, Clone, Copy)]
17pub enum FccConversionError {
18 TooLong,
20 TooShort,
22 InvalidChar,
24}
25
26impl FccConversionError {
27 pub fn description(&self) -> &str {
28 match self {
29 FccConversionError::TooLong => "four char code is too long",
30 FccConversionError::TooShort => "four char code is too short",
31 FccConversionError::InvalidChar => "invalid char in four char code",
32 }
33 }
34}
35
36impl fmt::Display for FccConversionError {
37 #[inline]
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 fmt::Display::fmt(FccConversionError::description(self), f)
40 }
41}
42
43#[cfg(feature = "std")]
44impl ::std::error::Error for FccConversionError {
45 #[inline]
46 fn description(&self) -> &str {
47 FccConversionError::description(self)
48 }
49}
50
51type Result<T> = core::result::Result<T, FccConversionError>;
52
53#[repr(transparent)]
55#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
56pub struct FourCharCode(u32);
57
58#[repr(transparent)]
60pub struct Display(u32);
61
62fn from_bytes(mut bytes: [u8; 4]) -> Result<FourCharCode> {
63 let mut null_streak = true;
64
65 let mut i = 3usize;
66 loop {
67 let mut c = bytes[i];
68 if c == 0 && null_streak {
69 c = 0x20;
70 bytes[i] = c;
71 } else {
72 null_streak = false;
73 }
74
75 if c > b'\x7f' {
76 return Err(FccConversionError::InvalidChar);
77 }
78
79 if i == 0 {
80 break;
81 }
82 i -= 1;
83 }
84
85 Ok(FourCharCode(u32::from_be_bytes(bytes)))
86}
87
88fn normalize(value: u32) -> u32 {
89 let mut bytes = u32::to_be_bytes(value);
90
91 let mut i = 3usize;
92 loop {
93 let c = bytes[i];
94
95 if c == 0 {
96 bytes[i] = 0x20;
97 } else {
98 return u32::from_be_bytes(bytes);
99 }
100
101 if i == 0 {
102 break;
103 }
104 i -= 1;
105 }
106
107 u32::from_be_bytes(bytes)
108}
109
110impl FourCharCode {
111 #[inline]
113 pub fn new(value: u32) -> Result<Self> {
114 from_bytes(u32::to_be_bytes(value))
115 }
116
117 #[inline]
121 pub const unsafe fn new_unchecked(value: u32) -> Self {
122 Self(value)
123 }
124
125 #[inline]
127 pub fn from_array(value: [u8; 4]) -> Result<Self> {
128 from_bytes(value)
129 }
130
131 pub fn from_slice(value: &[u8]) -> Result<Self> {
133 match value.len().cmp(&4) {
134 Ordering::Less => return Err(FccConversionError::TooShort),
135 Ordering::Greater => return Err(FccConversionError::TooLong),
136 _ => (),
137 }
138
139 from_bytes(unsafe {
140 [
141 *value.get_unchecked(0),
142 *value.get_unchecked(1),
143 *value.get_unchecked(2),
144 *value.get_unchecked(3),
145 ]
146 })
147 }
148
149 #[allow(clippy::should_implement_trait)]
151 pub fn from_str(value: &str) -> Result<Self> {
152 Self::from_slice(value.as_bytes())
153 }
154
155 pub fn normalize(&mut self) {
157 self.0 = normalize(self.0);
158 }
159
160 #[allow(clippy::trivially_copy_pass_by_ref)]
163 pub fn display(&self) -> Display {
164 Display(u32::from_be(normalize(self.0)))
165 }
166
167 #[inline]
169 pub const fn as_u32(&self) -> u32 {
170 self.0
171 }
172}
173
174impl Default for FourCharCode {
175 #[inline]
176 fn default() -> Self {
177 four_char_code!(" ")
178 }
179}
180
181impl PartialEq<u32> for FourCharCode {
182 #[inline]
183 fn eq(&self, other: &u32) -> bool {
184 self.0.eq(other)
185 }
186}
187
188impl PartialOrd<u32> for FourCharCode {
189 #[inline]
190 fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
191 self.0.partial_cmp(other)
192 }
193}
194
195impl PartialEq<str> for FourCharCode {
196 fn eq(&self, other: &str) -> bool {
197 if let Ok(other) = Self::from_str(other) {
198 *self == other
199 } else {
200 false
201 }
202 }
203}
204
205impl PartialEq<&str> for FourCharCode {
206 #[inline]
207 fn eq(&self, other: &&str) -> bool {
208 self.eq(*other)
209 }
210}
211
212impl PartialOrd<str> for FourCharCode {
213 fn partial_cmp(&self, other: &str) -> Option<Ordering> {
214 if let Ok(other) = Self::from_str(other) {
215 self.partial_cmp(&other)
216 } else {
217 None
218 }
219 }
220}
221
222impl PartialOrd<&str> for FourCharCode {
223 #[inline]
224 fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
225 self.partial_cmp(*other)
226 }
227}
228
229impl PartialEq<[u8]> for FourCharCode {
230 fn eq(&self, other: &[u8]) -> bool {
231 if let Ok(other) = Self::from_slice(other) {
232 *self == other
233 } else {
234 false
235 }
236 }
237}
238
239impl PartialEq<&[u8]> for FourCharCode {
240 #[inline]
241 fn eq(&self, other: &&[u8]) -> bool {
242 self.eq(*other)
243 }
244}
245
246impl PartialOrd<[u8]> for FourCharCode {
247 fn partial_cmp(&self, other: &[u8]) -> Option<Ordering> {
248 if let Ok(other) = Self::from_slice(other) {
249 self.partial_cmp(&other)
250 } else {
251 None
252 }
253 }
254}
255
256impl PartialOrd<&[u8]> for FourCharCode {
257 #[inline]
258 fn partial_cmp(&self, other: &&[u8]) -> Option<Ordering> {
259 self.partial_cmp(*other)
260 }
261}
262
263impl PartialEq<[u8; 4]> for FourCharCode {
264 fn eq(&self, other: &[u8; 4]) -> bool {
265 if let Ok(other) = Self::from_array(*other) {
266 *self == other
267 } else {
268 false
269 }
270 }
271}
272
273impl PartialOrd<[u8; 4]> for FourCharCode {
274 fn partial_cmp(&self, other: &[u8; 4]) -> Option<Ordering> {
275 if let Ok(other) = Self::from_array(*other) {
276 self.partial_cmp(&other)
277 } else {
278 None
279 }
280 }
281}
282
283impl fmt::Debug for FourCharCode {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 f.debug_tuple("FourCharCode")
286 .field(&self.display())
287 .finish()
288 }
289}
290
291#[cfg(feature = "std")]
292#[allow(clippy::to_string_trait_impl)]
293impl ToString for FourCharCode {
294 #[inline]
295 fn to_string(&self) -> String {
296 let bytes = self.0.to_be_bytes();
297 unsafe { core::str::from_utf8_unchecked(&bytes[..]) }.to_string()
298 }
299}
300
301impl From<FourCharCode> for u32 {
302 #[inline]
303 fn from(value: FourCharCode) -> Self {
304 value.0
305 }
306}
307
308#[cfg(feature = "std")]
309impl From<FourCharCode> for String {
310 #[inline]
311 fn from(value: FourCharCode) -> Self {
312 value.to_string()
313 }
314}
315
316impl fmt::Display for Display {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 let chars = unsafe { core::slice::from_raw_parts((&self.0 as *const u32) as *const u8, 4) };
319 for &c in chars {
320 let c = if c <= b'\x1f' || c >= b'\x7f' {
321 '�'
322 } else {
323 #[allow(clippy::transmute_int_to_char)]
324 unsafe {
325 core::mem::transmute::<u32, char>(u32::from(c))
326 }
327 };
328 fmt::Display::fmt(&c, f)?;
329 }
330 Ok(())
331 }
332}
333
334impl fmt::Debug for Display {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 let chars = unsafe { core::slice::from_raw_parts((&self.0 as *const u32) as *const u8, 4) };
337 f.write_char('"')?;
338 for &c in chars {
339 if c <= b'\x1f' || c >= b'\x7f' {
340 f.write_char('�')
341 } else if c == b'"' {
342 f.write_str("\\\"")
343 } else {
344 #[allow(clippy::transmute_int_to_char)]
345 f.write_char(unsafe { core::mem::transmute::<u32, char>(u32::from(c)) })
346 }?;
347 }
348 f.write_char('"')
349 }
350}
351
352#[doc(hidden)]
353#[cfg(ge_1_38_0)]
354pub mod __private {
355 use core::fmt::Write;
356
357 use super::{FccConversionError, FourCharCode};
358
359 struct FccBuf {
360 buf: [u8; 4],
361 len: usize,
362 err: Option<FccConversionError>,
363 }
364
365 impl FccBuf {
366 #[inline(always)]
367 fn new() -> Self {
368 Self {
369 buf: [0; 4],
370 len: 0,
371 err: None,
372 }
373 }
374 }
375
376 impl core::fmt::Write for FccBuf {
377 fn write_char(&mut self, c: char) -> core::fmt::Result {
378 if !c.is_ascii() || c.is_control() {
379 self.err = Some(FccConversionError::InvalidChar);
380 Err(core::fmt::Error)
381 } else if self.len == 4 {
382 self.err = Some(FccConversionError::TooLong);
383 Err(core::fmt::Error)
384 } else {
385 unsafe { *self.buf.get_unchecked_mut(self.len) = c as u8 };
386 self.len += 1;
387 Ok(())
388 }
389 }
390
391 #[inline]
392 fn write_fmt(mut self: &mut Self, args: core::fmt::Arguments<'_>) -> core::fmt::Result {
393 core::fmt::write(&mut self, args)
394 }
395
396 fn write_str(&mut self, s: &str) -> core::fmt::Result {
397 for c in s.chars() {
398 self.write_char(c)?;
399 }
400 Ok(())
401 }
402 }
403
404 pub fn fcc_format(
405 args: core::fmt::Arguments<'_>,
406 ) -> core::result::Result<FourCharCode, FccConversionError> {
407 let mut buf = FccBuf::new();
408 buf.write_fmt(args).map_err(|_| buf.err.take().unwrap())?;
409 if buf.len != 4 {
410 return Err(FccConversionError::TooShort);
411 }
412 Ok(FourCharCode(u32::from_be_bytes(buf.buf)))
413 }
414}
415
416#[derive(proc_macro_hack::ProcMacroHack)]
417enum _26four_char_code_macros_impl_14four_char_code {
418 Value = (
419 stringify! {
420 #[doc(hidden)]
421 pub use four_char_code_macros_impl::_proc_macro_hack_four_char_code;
422
423 #[macro_export]
425 macro_rules! four_char_code {
426 ($($proc_macro:tt)*) => {
427 {
428 #[derive($crate::_proc_macro_hack_four_char_code)]
429 #[allow(dead_code)]
430 enum ProcMacroHack {
431 Value = (stringify!($($proc_macro)*), 0).1
432 }
433 unsafe { $crate::FourCharCode::new_unchecked(proc_macro_call!()) }
434 }
435 };
436 }
437 },
438 0,
439 )
440 .1,
441}
442
443#[cfg(ge_1_38_0)]
444#[macro_export]
447macro_rules! fcc_format {
448 ($fmt:expr) => {
449 $crate::__private::fcc_format(::core::format_args!($fmt))
450 };
451 ($fmt:expr, $($args:tt)*) => {
452 $crate::__private::fcc_format(::core::format_args!($fmt, $($args)*))
453 };
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 const HEX: FourCharCode = four_char_code!("hex_");
461
462 #[test]
463 fn invalid() {
464 assert!(FourCharCode::new(128).is_err());
465 assert!(FourCharCode::from_str("").is_err());
466 assert!(FourCharCode::from_str("test1").is_err());
467 assert!(FourCharCode::from_slice(b"\x80___").is_err());
468 }
469
470 #[test]
471 fn valid() {
472 assert_eq!(HEX, "hex_");
473 let ui32 = FourCharCode::from_str("ui32");
474 assert!(ui32.is_ok());
475 assert_eq!(ui32.unwrap(), "ui32");
476 let zigr = FourCharCode::from_str("\0igr");
477 assert!(zigr.is_ok());
478 assert_eq!(zigr.unwrap(), "\0igr");
479 }
480
481 #[cfg(ge_1_38_0)]
482 #[test]
483 fn format() {
484 let f1mn = fcc_format!("F{}Mn", 1);
485 assert!(f1mn.is_ok());
486 assert_eq!(f1mn.unwrap(), "F1Mn");
487 }
488}