use std::{
fmt::{self, Write as _},
mem::transmute,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign},
slice,
str::FromStr,
};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
pub enum Char {
Null = 0,
StartOfHeading = 1,
StartOfText = 2,
EndOfText = 3,
EndOfTransmission = 4,
Enquiry = 5,
Acknowledge = 6,
Bell = 7,
Backspace = 8,
CharacterTabulation = 9,
LineFeed = 10,
LineTabulation = 11,
FormFeed = 12,
CarriageReturn = 13,
ShiftOut = 14,
ShiftIn = 15,
DataLinkEscape = 16,
DeviceControlOne = 17,
DeviceControlTwo = 18,
DeviceControlThree = 19,
DeviceControlFour = 20,
NegativeAcknowledge = 21,
SynchronousIdle = 22,
EndOfTransmissionBlock = 23,
Cancel = 24,
EndOfMedium = 25,
Substitute = 26,
Escape = 27,
InformationSeparatorFour = 28,
InformationSeparatorThree = 29,
InformationSeparatorTwo = 30,
InformationSeparatorOne = 31,
Space = 32,
ExclamationMark = 33,
QuotationMark = 34,
NumberSign = 35,
DollarSign = 36,
PercentSign = 37,
Ampersand = 38,
Apostrophe = 39,
LeftParenthesis = 40,
RightParenthesis = 41,
Asterisk = 42,
PlusSign = 43,
Comma = 44,
HyphenMinus = 45,
FullStop = 46,
Solidus = 47,
Digit0 = 48,
Digit1 = 49,
Digit2 = 50,
Digit3 = 51,
Digit4 = 52,
Digit5 = 53,
Digit6 = 54,
Digit7 = 55,
Digit8 = 56,
Digit9 = 57,
Colon = 58,
Semicolon = 59,
LessThanSign = 60,
EqualsSign = 61,
GreaterThanSign = 62,
QuestionMark = 63,
CommercialAt = 64,
CapitalA = 65,
CapitalB = 66,
CapitalC = 67,
CapitalD = 68,
CapitalE = 69,
CapitalF = 70,
CapitalG = 71,
CapitalH = 72,
CapitalI = 73,
CapitalJ = 74,
CapitalK = 75,
CapitalL = 76,
CapitalM = 77,
CapitalN = 78,
CapitalO = 79,
CapitalP = 80,
CapitalQ = 81,
CapitalR = 82,
CapitalS = 83,
CapitalT = 84,
CapitalU = 85,
CapitalV = 86,
CapitalW = 87,
CapitalX = 88,
CapitalY = 89,
CapitalZ = 90,
LeftSquareBracket = 91,
ReverseSolidus = 92,
RightSquareBracket = 93,
CircumflexAccent = 94,
LowLine = 95,
GraveAccent = 96,
SmallA = 97,
SmallB = 98,
SmallC = 99,
SmallD = 100,
SmallE = 101,
SmallF = 102,
SmallG = 103,
SmallH = 104,
SmallI = 105,
SmallJ = 106,
SmallK = 107,
SmallL = 108,
SmallM = 109,
SmallN = 110,
SmallO = 111,
SmallP = 112,
SmallQ = 113,
SmallR = 114,
SmallS = 115,
SmallT = 116,
SmallU = 117,
SmallV = 118,
SmallW = 119,
SmallX = 120,
SmallY = 121,
SmallZ = 122,
LeftCurlyBracket = 123,
VerticalLine = 124,
RightCurlyBracket = 125,
Tilde = 126,
Delete = 127,
}
impl Char {
#[inline]
pub const fn to_u8(self) -> u8 {
self as u8
}
pub const fn as_str(&self) -> &str {
Self::slice_as_str(slice::from_ref(self))
}
pub const fn slice_as_str(slice: &[Self]) -> &str {
unsafe {
std::str::from_utf8_unchecked(slice::from_raw_parts(slice.as_ptr().cast(), slice.len()))
}
}
pub fn slice_as_mut_str(slice: &mut [Self]) -> &mut str {
unsafe {
std::str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(
slice.as_mut_ptr().cast(),
slice.len(),
))
}
}
}
macro_rules! into_int_impl {
($($ty:ty)*) => {
$(
impl From<Char> for $ty {
#[inline]
fn from(chr: Char) -> $ty {
chr as u8 as $ty
}
}
)*
}
}
into_int_impl!(u8 u16 u32 u64 u128 char);
const HEX_DIGITS: [Char; 16] = [
Char::Digit0,
Char::Digit1,
Char::Digit2,
Char::Digit3,
Char::Digit4,
Char::Digit5,
Char::Digit6,
Char::Digit7,
Char::Digit8,
Char::Digit9,
Char::SmallA,
Char::SmallB,
Char::SmallC,
Char::SmallD,
Char::SmallE,
Char::SmallF,
];
macro_rules! impl_arith {
($trait:ident $fn:ident $trait_assign:ident $fn_assign:ident) => {
impl $trait<u8> for Char {
type Output = Char;
#[inline]
fn $fn(self, rhs: u8) -> Char {
u8::$fn(self.to_u8(), rhs).try_into().unwrap()
}
}
impl $trait_assign<u8> for Char {
#[inline]
fn $fn_assign(&mut self, rhs: u8) {
*self = Self::$fn(*self, rhs);
}
}
};
}
impl_arith!(Add add AddAssign add_assign);
impl_arith!(Sub sub SubAssign sub_assign);
impl_arith!(Mul mul MulAssign mul_assign);
impl_arith!(Div div DivAssign div_assign);
impl_arith!(Rem rem RemAssign rem_assign);
pub(crate) fn write_escaped(c: Char, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Char::{
Apostrophe, CarriageReturn, CharacterTabulation, LineFeed, Null,
ReverseSolidus as Backslash, ReverseSolidus, SmallX,
};
fn backslash(a: Char) -> ([Char; 4], usize) {
([Backslash, a, Null, Null], 2)
}
let (buf, len) = match c {
Null => backslash(Char::Digit0),
CharacterTabulation => backslash(Char::SmallT),
CarriageReturn => backslash(Char::SmallR),
LineFeed => backslash(Char::SmallN),
ReverseSolidus => backslash(ReverseSolidus),
Apostrophe => backslash(Apostrophe),
_ if c.to_u8().is_ascii_control() => {
let byte = c.to_u8();
let hi = HEX_DIGITS[usize::from(byte >> 4)];
let lo = HEX_DIGITS[usize::from(byte & 0xf)];
([Backslash, SmallX, hi, lo], 4)
}
_ => ([c, Null, Null, Null], 1),
};
f.write_str(Char::slice_as_str(&buf[..len]))
}
pub(crate) fn write_escaped_quoted(c: Char, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("'")?;
write_escaped(c, f)?;
f.write_str("'")?;
Ok(())
}
impl fmt::Debug for Char {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_escaped_quoted(*self, f)
}
}
impl fmt::Display for Char {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_char((*self).into())
}
}
impl PartialEq<u8> for Char {
#[inline]
fn eq(&self, other: &u8) -> bool {
u8::eq(&(*self).into(), other)
}
}
impl PartialEq<Char> for u8 {
#[inline]
fn eq(&self, other: &Char) -> bool {
Char::eq(other, self)
}
}
impl PartialEq<char> for Char {
#[inline]
fn eq(&self, other: &char) -> bool {
char::eq(&(*self).into(), other)
}
}
impl PartialEq<Char> for char {
#[inline]
fn eq(&self, other: &Char) -> bool {
Char::eq(other, self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
Length(usize),
Byte(u8),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Length(len) => write!(f, "invalid length: {}", len),
Self::Byte(ch) => write!(f, "invalid byte: {:#x?}", ch),
}
}
}
impl std::error::Error for Error {}
impl TryFrom<u8> for Char {
type Error = u8;
#[inline]
fn try_from(value: u8) -> Result<Self, u8> {
if value > 127 {
return Err(value);
}
Ok(unsafe { transmute::<u8, Char>(value) })
}
}
impl FromStr for Char {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.as_bytes();
if bytes.len() != 1 {
return Err(Error::Length(bytes.len()));
}
let byte = bytes[0];
byte.try_into().map_err(Error::Byte)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::unwrap;
#[test]
#[should_panic = "invalid byte: 0x80"]
fn try_from_u8() {
let _ = unwrap!(Char::try_from(128).map_err(Error::Byte));
}
}