1use core::fmt;
2use core::ops::Range;
3
4pub const MAX_CODE_UNITS: usize = isize::MAX as usize / size_of::<u16>();
8
9#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
20pub struct CodeUnitOffset(usize);
21
22impl CodeUnitOffset {
23 pub const fn new(value: usize) -> Self {
25 Self(value)
26 }
27
28 pub const fn get(self) -> usize {
30 self.0
31 }
32}
33
34#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
36pub struct ScalarOffset(usize);
37
38impl ScalarOffset {
39 pub const fn new(value: usize) -> Self {
41 Self(value)
42 }
43
44 pub const fn get(self) -> usize {
46 self.0
47 }
48}
49
50#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
52pub struct CodeUnitRange {
53 pub start: CodeUnitOffset,
55 pub end: CodeUnitOffset,
57}
58
59impl CodeUnitRange {
60 pub const fn new(start: CodeUnitOffset, end: CodeUnitOffset) -> Self {
62 Self { start, end }
63 }
64}
65
66#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
68pub struct ScalarRange {
69 pub start: ScalarOffset,
71 pub end: ScalarOffset,
73}
74
75impl ScalarRange {
76 pub const fn new(start: ScalarOffset, end: ScalarOffset) -> Self {
78 Self { start, end }
79 }
80}
81
82#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
84pub struct InvalidSurrogate {
85 pub offset: CodeUnitOffset,
87 pub unit: u16,
89}
90
91#[derive(Clone, Debug, Eq, PartialEq)]
93pub enum CodeUnitStringError {
94 TooLong {
96 len: usize,
98 max: usize,
100 },
101 LoneSurrogate(InvalidSurrogate),
103}
104
105impl fmt::Display for CodeUnitStringError {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match self {
108 Self::TooLong { len, max } => {
109 write!(
110 f,
111 "code-unit length {len} exceeds the supported maximum {max}"
112 )
113 }
114 Self::LoneSurrogate(invalid) => {
115 write!(
116 f,
117 "lone surrogate {:#06x} at code-unit index {}",
118 invalid.unit,
119 invalid.offset.get()
120 )
121 }
122 }
123 }
124}
125
126impl std::error::Error for CodeUnitStringError {}
127
128#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
133pub struct CodeUnitString {
134 units: Vec<u16>,
135}
136
137impl CodeUnitString {
138 pub fn from_scalar(text: &str) -> Self {
140 Self {
141 units: text.encode_utf16().collect(),
142 }
143 }
144
145 pub fn try_from_code_units(units: Vec<u16>) -> Result<Self, CodeUnitStringError> {
147 if units.len() > MAX_CODE_UNITS {
148 return Err(CodeUnitStringError::TooLong {
149 len: units.len(),
150 max: MAX_CODE_UNITS,
151 });
152 }
153 Ok(Self { units })
154 }
155
156 pub fn from_code_units(units: Vec<u16>) -> Self {
161 Self::try_from_code_units(units).expect("an existing Vec satisfies allocation limits")
162 }
163
164 pub fn as_code_units(&self) -> &[u16] {
166 &self.units
167 }
168
169 pub fn len(&self) -> usize {
171 self.units.len()
172 }
173
174 pub fn is_empty(&self) -> bool {
176 self.units.is_empty()
177 }
178
179 pub fn code_unit_at(&self, offset: CodeUnitOffset) -> Option<u16> {
181 self.units.get(offset.get()).copied()
182 }
183
184 pub fn slice(&self, range: CodeUnitRange) -> Self {
186 let start = range.start.get().min(self.len());
187 let end = range.end.get().max(start).min(self.len());
188 Self::from_code_units(self.units[Range { start, end }].to_vec())
189 }
190
191 pub fn code_units(&self) -> impl ExactSizeIterator<Item = u16> + '_ {
193 self.units.iter().copied()
194 }
195
196 pub fn iter_code_points(&self) -> CodePointIter<'_> {
198 CodePointIter {
199 units: &self.units,
200 at: 0,
201 }
202 }
203
204 pub fn to_scalar(&self) -> Result<String, CodeUnitStringError> {
206 String::from_utf16(&self.units).map_err(|_| {
207 let invalid = first_lone(&self.units).expect("invalid UTF-16 has a lone surrogate");
208 CodeUnitStringError::LoneSurrogate(invalid)
209 })
210 }
211
212 pub fn code_unit_offset(
214 &self,
215 scalar: ScalarOffset,
216 ) -> Result<CodeUnitOffset, OffsetConversionError> {
217 let text = self
218 .to_scalar()
219 .map_err(OffsetConversionError::InvalidText)?;
220 let mut scalar_at = 0;
221 for (byte_at, _) in text.char_indices() {
222 if scalar_at == scalar.get() {
223 return Ok(CodeUnitOffset::new(text[..byte_at].encode_utf16().count()));
224 }
225 scalar_at += 1;
226 }
227 if scalar_at == scalar.get() {
228 return Ok(CodeUnitOffset::new(self.len()));
229 }
230 Err(OffsetConversionError::ScalarOutOfBounds {
231 offset: scalar,
232 len: scalar_at,
233 })
234 }
235
236 pub fn scalar_offset(
238 &self,
239 code_unit: CodeUnitOffset,
240 ) -> Result<ScalarOffset, OffsetConversionError> {
241 if code_unit.get() > self.len() {
242 return Err(OffsetConversionError::CodeUnitOutOfBounds {
243 offset: code_unit,
244 len: self.len(),
245 });
246 }
247 let text = self
248 .to_scalar()
249 .map_err(OffsetConversionError::InvalidText)?;
250 let mut units = 0;
251 let mut scalars = 0;
252 for scalar in text.chars() {
253 if units == code_unit.get() {
254 return Ok(ScalarOffset::new(scalars));
255 }
256 units += scalar.len_utf16();
257 scalars += 1;
258 if units > code_unit.get() {
259 return Err(OffsetConversionError::NotScalarBoundary(code_unit));
260 }
261 }
262 Ok(ScalarOffset::new(scalars))
263 }
264}
265
266#[derive(Clone, Debug, Eq, PartialEq)]
268pub enum OffsetConversionError {
269 InvalidText(CodeUnitStringError),
271 CodeUnitOutOfBounds { offset: CodeUnitOffset, len: usize },
273 ScalarOutOfBounds { offset: ScalarOffset, len: usize },
275 NotScalarBoundary(CodeUnitOffset),
277}
278
279impl AsRef<[u16]> for CodeUnitString {
280 fn as_ref(&self) -> &[u16] {
281 self.as_code_units()
282 }
283}
284
285pub struct CodePointIter<'a> {
287 units: &'a [u16],
288 at: usize,
289}
290
291impl Iterator for CodePointIter<'_> {
292 type Item = CodeUnitString;
293
294 fn next(&mut self) -> Option<Self::Item> {
295 let first = *self.units.get(self.at)?;
296 let width = if (0xd800..=0xdbff).contains(&first)
297 && self
298 .units
299 .get(self.at + 1)
300 .is_some_and(|unit| (0xdc00..=0xdfff).contains(unit))
301 {
302 2
303 } else {
304 1
305 };
306 let out = CodeUnitString::from_code_units(self.units[self.at..self.at + width].to_vec());
307 self.at += width;
308 Some(out)
309 }
310}
311
312fn first_lone(units: &[u16]) -> Option<InvalidSurrogate> {
313 let mut index = 0;
314 while index < units.len() {
315 let unit = units[index];
316 if (0xd800..=0xdbff).contains(&unit) {
317 if units
318 .get(index + 1)
319 .is_some_and(|next| (0xdc00..=0xdfff).contains(next))
320 {
321 index += 2;
322 continue;
323 }
324 return Some(InvalidSurrogate {
325 offset: CodeUnitOffset::new(index),
326 unit,
327 });
328 }
329 if (0xdc00..=0xdfff).contains(&unit) {
330 return Some(InvalidSurrogate {
331 offset: CodeUnitOffset::new(index),
332 unit,
333 });
334 }
335 index += 1;
336 }
337 None
338}