use std::{
char,
fmt::Display,
ops::{Add, AddAssign, Sub},
};
use irange::integer::Bounded;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub(crate) const INVALID_MIN: u32 = 0xD800;
pub(crate) const INVALID_MAX: u32 = 0xDFFF;
pub(crate) const INVALID_SIZE: u32 = INVALID_MAX - INVALID_MIN + 1;
const fn to_index(codepoint: u32) -> u32 {
if codepoint >= INVALID_MIN {
codepoint - INVALID_SIZE
} else {
codepoint
}
}
const fn from_index(index: u32) -> u32 {
if index >= INVALID_MIN {
index + INVALID_SIZE
} else {
index
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Char(char);
impl Char {
pub const COUNT: u32 = char::MAX as u32 + 1 - INVALID_SIZE;
#[inline]
pub fn new(c: char) -> Self {
Char(c)
}
#[inline]
pub fn to_char(&self) -> char {
self.0
}
#[inline]
pub fn from_u32(c: u32) -> Option<Self> {
Some(Char(char::from_u32(c)?))
}
#[inline]
pub fn to_u32(&self) -> u32 {
self.0 as u32
}
#[inline]
pub(crate) fn next(self) -> Option<Self> {
Self::from_index(self.index() + 1)
}
#[inline]
pub(crate) fn previous(self) -> Option<Self> {
Self::from_index(self.index().checked_sub(1)?)
}
#[inline]
pub(crate) fn index(self) -> u32 {
to_index(self.to_u32())
}
#[inline]
pub(crate) fn from_index(index: u32) -> Option<Self> {
Self::from_u32(from_index(index))
}
}
impl Display for Char {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if ('\u{20}'..='\u{7E}').contains(&self.0) {
write!(f, "{}", self.0)
} else {
write!(f, "\\u{{{:04x}}}", self.to_u32())
}
}
}
impl Add<Char> for Char {
type Output = Char;
fn add(self, rhs: Self) -> Self::Output {
Char::from_index(self.index() + rhs.index()).expect("attempt to add with overflow")
}
}
impl Sub<Char> for Char {
type Output = Char;
fn sub(self, rhs: Self) -> Self::Output {
let index = self
.index()
.checked_sub(rhs.index())
.expect("attempt to subtract with overflow");
Char::from_index(index).expect("attempt to subtract with overflow")
}
}
impl AddAssign for Char {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl Bounded for Char {
#[inline]
fn min_value() -> Self {
Char('\0')
}
#[inline]
fn max_value() -> Self {
Char(char::MAX)
}
#[inline]
fn one() -> Self {
Char('\u{1}')
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn char_add() {
assert_eq!(Char::new('\u{3}'), Char::new('\u{2}') + Char::one());
assert_eq!(Char::new('\u{E000}'), Char::new('\u{D7FF}') + Char::one());
assert_eq!(Char::new('\u{E001}'), Char::new('\u{E000}') + Char::one());
}
#[test]
fn char_add_assign() {
let mut c = Char::new('\u{2}');
c += Char::one();
assert_eq!(Char::new('\u{3}'), c);
let mut c = Char::new('\u{D7FF}');
c += Char::one();
assert_eq!(Char::new('\u{E000}'), c);
let mut c = Char::new('\u{E000}');
c += Char::one();
assert_eq!(Char::new('\u{E001}'), c);
}
#[test]
fn char_sub() {
assert_eq!(Char::new('\u{2}'), Char::new('\u{3}') - Char::one());
assert_eq!(Char::new('\u{D7FF}'), Char::new('\u{E000}') - Char::one());
assert_eq!(Char::new('\u{E000}'), Char::new('\u{E001}') - Char::one());
}
#[test]
fn add_and_sub_are_inverses_across_the_surrogate_block() {
for a in [0u32, 1, 0xD7FE, 0xD7FF, 0xE000, 0xE001, 0x10FFFF] {
for b in [0u32, 1, 0x1000, 0xD7FF, 0xE000] {
let (a, b) = (Char::from_u32(a).unwrap(), Char::from_u32(b).unwrap());
if a.index() < b.index() {
continue;
}
assert_eq!(a, (a - b) + b, "({a} - {b}) + {b}");
}
}
}
#[test]
fn next_and_previous_skip_the_surrogate_block() {
assert_eq!(Some(Char::new('\u{E000}')), Char::new('\u{D7FF}').next());
assert_eq!(
Some(Char::new('\u{D7FF}')),
Char::new('\u{E000}').previous()
);
assert_eq!(None, Char::max_value().next());
assert_eq!(None, Char::min_value().previous());
}
#[test]
fn indices_are_contiguous() {
assert_eq!(0, Char::min_value().index());
assert_eq!(Char::COUNT - 1, Char::max_value().index());
assert_eq!(
Char::new('\u{D7FF}').index() + 1,
Char::new('\u{E000}').index()
);
}
#[test]
#[should_panic(expected = "attempt to add with overflow")]
fn add_past_the_last_char_panics() {
let _ = Char::max_value() + Char::one();
}
#[test]
#[should_panic(expected = "attempt to subtract with overflow")]
fn sub_below_the_first_char_panics() {
let _ = Char::min_value() - Char::one();
}
#[test]
fn display_covers_printable_ascii() {
assert_eq!("~", Char::new('~').to_string());
assert_eq!(" ", Char::new(' ').to_string());
assert_eq!("\\u{007f}", Char::new('\u{7F}').to_string());
assert_eq!("\\u{000a}", Char::new('\n').to_string());
}
}