use core::fmt;
use core::ops::Range;
pub const MAX_CODE_UNITS: usize = isize::MAX as usize / size_of::<u16>();
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CodeUnitOffset(usize);
impl CodeUnitOffset {
pub const fn new(value: usize) -> Self {
Self(value)
}
pub const fn get(self) -> usize {
self.0
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ScalarOffset(usize);
impl ScalarOffset {
pub const fn new(value: usize) -> Self {
Self(value)
}
pub const fn get(self) -> usize {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CodeUnitRange {
pub start: CodeUnitOffset,
pub end: CodeUnitOffset,
}
impl CodeUnitRange {
pub const fn new(start: CodeUnitOffset, end: CodeUnitOffset) -> Self {
Self { start, end }
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ScalarRange {
pub start: ScalarOffset,
pub end: ScalarOffset,
}
impl ScalarRange {
pub const fn new(start: ScalarOffset, end: ScalarOffset) -> Self {
Self { start, end }
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct InvalidSurrogate {
pub offset: CodeUnitOffset,
pub unit: u16,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CodeUnitStringError {
TooLong {
len: usize,
max: usize,
},
LoneSurrogate(InvalidSurrogate),
}
impl fmt::Display for CodeUnitStringError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooLong { len, max } => {
write!(
f,
"code-unit length {len} exceeds the supported maximum {max}"
)
}
Self::LoneSurrogate(invalid) => {
write!(
f,
"lone surrogate {:#06x} at code-unit index {}",
invalid.unit,
invalid.offset.get()
)
}
}
}
}
impl std::error::Error for CodeUnitStringError {}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct CodeUnitString {
units: Vec<u16>,
}
impl CodeUnitString {
pub fn from_scalar(text: &str) -> Self {
Self {
units: text.encode_utf16().collect(),
}
}
pub fn try_from_code_units(units: Vec<u16>) -> Result<Self, CodeUnitStringError> {
if units.len() > MAX_CODE_UNITS {
return Err(CodeUnitStringError::TooLong {
len: units.len(),
max: MAX_CODE_UNITS,
});
}
Ok(Self { units })
}
pub fn from_code_units(units: Vec<u16>) -> Self {
Self::try_from_code_units(units).expect("an existing Vec satisfies allocation limits")
}
pub fn as_code_units(&self) -> &[u16] {
&self.units
}
pub fn len(&self) -> usize {
self.units.len()
}
pub fn is_empty(&self) -> bool {
self.units.is_empty()
}
pub fn code_unit_at(&self, offset: CodeUnitOffset) -> Option<u16> {
self.units.get(offset.get()).copied()
}
pub fn slice(&self, range: CodeUnitRange) -> Self {
let start = range.start.get().min(self.len());
let end = range.end.get().max(start).min(self.len());
Self::from_code_units(self.units[Range { start, end }].to_vec())
}
pub fn code_units(&self) -> impl ExactSizeIterator<Item = u16> + '_ {
self.units.iter().copied()
}
pub fn iter_code_points(&self) -> CodePointIter<'_> {
CodePointIter {
units: &self.units,
at: 0,
}
}
pub fn to_scalar(&self) -> Result<String, CodeUnitStringError> {
String::from_utf16(&self.units).map_err(|_| {
let invalid = first_lone(&self.units).expect("invalid UTF-16 has a lone surrogate");
CodeUnitStringError::LoneSurrogate(invalid)
})
}
pub fn code_unit_offset(
&self,
scalar: ScalarOffset,
) -> Result<CodeUnitOffset, OffsetConversionError> {
let text = self
.to_scalar()
.map_err(OffsetConversionError::InvalidText)?;
let mut scalar_at = 0;
for (byte_at, _) in text.char_indices() {
if scalar_at == scalar.get() {
return Ok(CodeUnitOffset::new(text[..byte_at].encode_utf16().count()));
}
scalar_at += 1;
}
if scalar_at == scalar.get() {
return Ok(CodeUnitOffset::new(self.len()));
}
Err(OffsetConversionError::ScalarOutOfBounds {
offset: scalar,
len: scalar_at,
})
}
pub fn scalar_offset(
&self,
code_unit: CodeUnitOffset,
) -> Result<ScalarOffset, OffsetConversionError> {
if code_unit.get() > self.len() {
return Err(OffsetConversionError::CodeUnitOutOfBounds {
offset: code_unit,
len: self.len(),
});
}
let text = self
.to_scalar()
.map_err(OffsetConversionError::InvalidText)?;
let mut units = 0;
let mut scalars = 0;
for scalar in text.chars() {
if units == code_unit.get() {
return Ok(ScalarOffset::new(scalars));
}
units += scalar.len_utf16();
scalars += 1;
if units > code_unit.get() {
return Err(OffsetConversionError::NotScalarBoundary(code_unit));
}
}
Ok(ScalarOffset::new(scalars))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OffsetConversionError {
InvalidText(CodeUnitStringError),
CodeUnitOutOfBounds { offset: CodeUnitOffset, len: usize },
ScalarOutOfBounds { offset: ScalarOffset, len: usize },
NotScalarBoundary(CodeUnitOffset),
}
impl AsRef<[u16]> for CodeUnitString {
fn as_ref(&self) -> &[u16] {
self.as_code_units()
}
}
pub struct CodePointIter<'a> {
units: &'a [u16],
at: usize,
}
impl Iterator for CodePointIter<'_> {
type Item = CodeUnitString;
fn next(&mut self) -> Option<Self::Item> {
let first = *self.units.get(self.at)?;
let width = if (0xd800..=0xdbff).contains(&first)
&& self
.units
.get(self.at + 1)
.is_some_and(|unit| (0xdc00..=0xdfff).contains(unit))
{
2
} else {
1
};
let out = CodeUnitString::from_code_units(self.units[self.at..self.at + width].to_vec());
self.at += width;
Some(out)
}
}
fn first_lone(units: &[u16]) -> Option<InvalidSurrogate> {
let mut index = 0;
while index < units.len() {
let unit = units[index];
if (0xd800..=0xdbff).contains(&unit) {
if units
.get(index + 1)
.is_some_and(|next| (0xdc00..=0xdfff).contains(next))
{
index += 2;
continue;
}
return Some(InvalidSurrogate {
offset: CodeUnitOffset::new(index),
unit,
});
}
if (0xdc00..=0xdfff).contains(&unit) {
return Some(InvalidSurrogate {
offset: CodeUnitOffset::new(index),
unit,
});
}
index += 1;
}
None
}