use core::fmt::Formatter;
use core::num::NonZeroU16;
#[derive(Debug, PartialEq, Clone, Copy, Hash, Eq)]
#[non_exhaustive]
pub struct Utf16CodeUnitError;
impl core::fmt::Display for Utf16CodeUnitError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
write!(f, "code unit does not fit the requested position in UTF-16")
}
}
impl core::error::Error for Utf16CodeUnitError {}
macro_rules! surrogate_impl {
($min:expr,
$max:expr,
$(#[$is_meta:meta])*,
$(#[$unchecked_meta:meta])*,
$(#[$checked_meta:meta])*,
) => {
pub const MIN: Self = const {
if $min == 0 {
panic!();
}
Self {
code_unit: if let Some(min) = NonZeroU16::new($min) {
min
} else {
panic!();
},
}
};
pub const MAX: Self = const {
Self {
code_unit: if let Some(max) = NonZeroU16::new($max) {
max
} else {
panic!();
},
}
};
$(#[$is_meta])*
#[inline(always)]
pub const fn is(code_unit: u16) -> bool {
code_unit >= Self::MIN.to_u16() && code_unit <= Self::MAX.to_u16()
}
$(#[$unchecked_meta])*
#[inline(always)]
pub const unsafe fn new_unchecked(code_unit: u16) -> Self {
debug_assert!(Self::is(code_unit));
Self { code_unit: unsafe { NonZeroU16::new_unchecked(code_unit) } }
}
$(#[$checked_meta])*
#[inline(always)]
pub const fn try_new(code_unit: u16) -> Result<Self, Utf16CodeUnitError> {
if Self::is(code_unit) {
Ok(unsafe { Self::new_unchecked(code_unit) })
} else {
Err(Utf16CodeUnitError)
}
}
#[inline(always)]
pub const fn to_u16(self) -> u16 {
self.code_unit.get()
}
};
}
macro_rules! to_surrogate {
() => {
#[inline(always)]
pub const fn to_surrogate(self) -> Surrogate {
Surrogate {
code_unit: self.code_unit,
}
}
};
}
macro_rules! code_unit_impls {
($type:ident,
$type_specific_ident:ident,
) => {
impl TryFrom<u16> for $type {
type Error = Utf16CodeUnitError;
#[inline(always)]
fn try_from(code_unit: u16) -> Result<Self, Utf16CodeUnitError> {
Self::try_new(code_unit)
}
}
impl From<$type> for u16 {
#[inline(always)]
fn from($type_specific_ident: $type) -> Self {
$type_specific_ident.to_u16()
}
}
};
}
trait Seal {}
#[allow(private_bounds)] pub trait Utf16CodeUnitSequence: Seal + Copy + Clone + Into<char> + AsRef<[u16]> {}
macro_rules! sequence_impls {
($type:ident,
$type_specific_ident:ident,
) => {
impl Seal for $type {}
impl Utf16CodeUnitSequence for $type {}
impl From<$type> for char {
#[inline(always)]
fn from($type_specific_ident: $type) -> Self {
$type_specific_ident.to_char()
}
}
impl AsRef<[u16]> for $type {
#[inline(always)]
fn as_ref(&self) -> &[u16] {
self.as_slice()
}
}
};
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct Bmp {
code_unit: u16, }
impl Bmp {
#[inline(always)]
pub const fn is(code_unit: u16) -> bool {
!Surrogate::is(code_unit)
}
#[inline(always)]
pub const unsafe fn new_unchecked(code_unit: u16) -> Self {
debug_assert!(Self::is(code_unit));
Self { code_unit }
}
#[inline(always)]
pub const fn try_new(code_unit: u16) -> Result<Self, Utf16CodeUnitError> {
if Self::is(code_unit) {
Ok(unsafe { Self::new_unchecked(code_unit) })
} else {
Err(Utf16CodeUnitError)
}
}
#[inline(always)]
pub const fn to_u16(self) -> u16 {
self.code_unit
}
#[inline(always)]
pub const fn to_char(self) -> char {
let scalar = self.code_unit as u32;
debug_assert!(char::from_u32(scalar).is_some());
unsafe { char::from_u32_unchecked(scalar) }
}
#[inline(always)]
pub const fn as_slice(&self) -> &[u16] {
core::slice::from_ref(&self.code_unit)
}
}
code_unit_impls!(Bmp, bmp,);
sequence_impls!(Bmp, bmp,);
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct Surrogate {
code_unit: NonZeroU16,
}
impl Surrogate {
surrogate_impl!(
0xD800,
0xDFFF,
,
,
,
);
}
code_unit_impls!(Surrogate, surrogate,);
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct HighSurrogate {
code_unit: NonZeroU16,
}
impl HighSurrogate {
surrogate_impl!(
0xD800,
0xDBFF,
,
,
,
);
to_surrogate!();
#[inline(always)]
pub const fn try_new_with_surrogate(surrogate: Surrogate) -> Result<Self, Utf16CodeUnitError> {
let code_unit = surrogate.to_u16();
if code_unit <= Self::MAX.to_u16() {
Ok(unsafe { Self::new_unchecked(code_unit) })
} else {
Err(Utf16CodeUnitError)
}
}
}
code_unit_impls!(HighSurrogate, high_surrogate,);
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct LowSurrogate {
code_unit: NonZeroU16,
}
impl LowSurrogate {
surrogate_impl!(
0xDC00,
0xDFFF,
,
,
,
);
to_surrogate!();
#[inline(always)]
pub const fn try_new_with_surrogate(surrogate: Surrogate) -> Result<Self, Utf16CodeUnitError> {
let code_unit = surrogate.to_u16();
if code_unit >= Self::MIN.to_u16() {
Ok(unsafe { Self::new_unchecked(code_unit) })
} else {
Err(Utf16CodeUnitError)
}
}
}
code_unit_impls!(LowSurrogate, low_surrogate,);
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[repr(C)]
pub struct SurrogatePair {
high: HighSurrogate,
low: LowSurrogate,
}
impl SurrogatePair {
#[inline(always)]
pub const fn new(high: HighSurrogate, low: LowSurrogate) -> Self {
Self { high, low }
}
#[inline(always)]
pub const fn to_typed_code_units(self) -> (HighSurrogate, LowSurrogate) {
(self.high, self.low)
}
#[inline(always)]
pub const fn to_char(self) -> char {
let scalar = ((self.high.to_u16() as u32) << 10) + (self.low.to_u16() as u32)
- (((0xD800u32 << 10) - 0x10000u32) + 0xDC00u32);
debug_assert!(char::from_u32(scalar).is_some());
unsafe { char::from_u32_unchecked(scalar) }
}
#[inline(always)]
pub const fn as_code_units(&self) -> &[u16; 2] {
if core::mem::size_of::<Self>() != core::mem::size_of::<[u16; 2]>() {
panic!();
}
let ptr: *const Self = core::ptr::from_ref(self);
unsafe { &*ptr.cast() }
}
#[inline(always)]
pub const fn as_slice(&self) -> &[u16] {
self.as_code_units()
}
}
sequence_impls!(SurrogatePair, surrogate_pair,);
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum BmpOrSurrogate {
Bmp(Bmp),
Surrogate(Surrogate),
}
impl BmpOrSurrogate {
#[inline(always)]
pub const fn new(code_unit: u16) -> Self {
if Bmp::is(code_unit) {
Self::Bmp(unsafe { Bmp::new_unchecked(code_unit) })
} else {
Self::Surrogate(unsafe { Surrogate::new_unchecked(code_unit) })
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_code_unit_ranges() {
for u in 0..=0xFFFFu16 {
let surrogate = (0xD800..=0xDFFF).contains(&u);
let high = (0xD800..=0xDBFF).contains(&u);
let low = (0xDC00..=0xDFFF).contains(&u);
assert_eq!(Bmp::is(u), !surrogate);
assert_eq!(Surrogate::is(u), surrogate);
assert_eq!(HighSurrogate::is(u), high);
assert_eq!(LowSurrogate::is(u), low);
assert_eq!(
Bmp::try_new(u).map(Bmp::to_u16),
Bmp::is(u).then_some(u).ok_or(Utf16CodeUnitError)
);
assert_eq!(
Surrogate::try_new(u).map(Surrogate::to_u16),
surrogate.then_some(u).ok_or(Utf16CodeUnitError)
);
assert_eq!(
HighSurrogate::try_new(u).map(HighSurrogate::to_u16),
high.then_some(u).ok_or(Utf16CodeUnitError)
);
assert_eq!(
LowSurrogate::try_new(u).map(LowSurrogate::to_u16),
low.then_some(u).ok_or(Utf16CodeUnitError)
);
match BmpOrSurrogate::new(u) {
BmpOrSurrogate::Bmp(bmp) => {
assert!(!surrogate);
assert_eq!(bmp.to_u16(), u);
assert_eq!(Some(bmp.to_char()), char::from_u32(u as u32));
assert_eq!(bmp.as_slice(), &[u]);
}
BmpOrSurrogate::Surrogate(s) => {
assert!(surrogate);
assert_eq!(s.to_u16(), u);
assert_eq!(
HighSurrogate::try_new_with_surrogate(s).map(HighSurrogate::to_u16),
high.then_some(u).ok_or(Utf16CodeUnitError)
);
assert_eq!(
LowSurrogate::try_new_with_surrogate(s).map(LowSurrogate::to_u16),
low.then_some(u).ok_or(Utf16CodeUnitError)
);
if let Ok(h) = HighSurrogate::try_new(u) {
assert_eq!(h.to_surrogate(), s);
}
if let Ok(l) = LowSurrogate::try_new(u) {
assert_eq!(l.to_surrogate(), s);
}
}
}
}
}
#[test]
fn test_surrogate_pairs() {
let mut previous: Option<(SurrogatePair, char)> = None;
for high in 0xD800..=0xDBFFu16 {
for low in 0xDC00..=0xDFFFu16 {
let pair = SurrogatePair::new(
HighSurrogate::try_new(high).unwrap(),
LowSurrogate::try_new(low).unwrap(),
);
let expected = char::decode_utf16([high, low]).next().unwrap().unwrap();
let c = pair.to_char();
assert_eq!(c, expected);
assert_eq!(pair.as_slice(), &[high, low]);
let (h, l) = pair.to_typed_code_units();
assert_eq!((h.to_u16(), l.to_u16()), (high, low));
if let Some((previous_pair, previous_char)) = previous {
assert!(previous_pair < pair);
assert!(previous_char < c);
}
previous = Some((pair, c));
}
}
assert_eq!(previous.unwrap().1, char::MAX);
}
#[test]
fn test_niche() {
assert_ne!(
core::mem::size_of::<Option<Bmp>>(),
core::mem::size_of::<Bmp>()
);
assert_eq!(
core::mem::size_of::<Option<Surrogate>>(),
core::mem::size_of::<Surrogate>()
);
assert_eq!(
core::mem::size_of::<Option<HighSurrogate>>(),
core::mem::size_of::<HighSurrogate>()
);
assert_eq!(
core::mem::size_of::<Option<LowSurrogate>>(),
core::mem::size_of::<LowSurrogate>()
);
assert_eq!(
core::mem::size_of::<Option<SurrogatePair>>(),
core::mem::size_of::<SurrogatePair>()
);
}
}