1use alloc::collections::TryReserveError;
2use core::fmt;
3use core::ops::Range;
4use core::slice::SliceIndex;
5
6use bstr::ByteSlice;
7use scolapasta_strbuf::Buf;
8
9use crate::case_folding::CaseFoldingEffect;
10use crate::codepoints::InvalidCodepointError;
11use crate::enc::binascii;
12use crate::encoding::Encoding;
13use crate::iter::{Bytes, IntoIter, Iter, IterMut};
14use crate::ord::OrdError;
15
16mod codepoints;
17mod eq;
18mod impls;
19mod inspect;
20#[cfg(feature = "std")]
21mod io;
22
23pub use codepoints::Codepoints;
24pub use inspect::Inspect;
25
26#[repr(transparent)]
27#[derive(Default, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
28pub struct AsciiString {
29 inner: Buf,
30}
31
32impl AsciiString {
34 pub const fn new(buf: Buf) -> Self {
35 Self { inner: buf }
36 }
37}
38
39impl fmt::Debug for AsciiString {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 f.debug_struct("AsciiString")
42 .field("buf", &self.inner.as_bstr())
43 .finish()
44 }
45}
46
47impl AsciiString {
49 #[inline]
50 #[must_use]
51 pub fn into_buf(self) -> Buf {
52 self.inner
53 }
54
55 #[inline]
56 #[must_use]
57 pub fn as_slice(&self) -> &[u8] {
58 self.inner.as_slice()
59 }
60
61 #[inline]
62 #[must_use]
63 pub fn as_mut_slice(&mut self) -> &mut [u8] {
64 self.inner.as_mut_slice()
65 }
66
67 #[inline]
68 #[must_use]
69 pub fn as_ptr(&self) -> *const u8 {
70 self.inner.as_ptr()
71 }
72
73 #[inline]
74 #[must_use]
75 pub fn as_mut_ptr(&mut self) -> *mut u8 {
76 self.inner.as_mut_ptr()
77 }
78}
79
80impl AsciiString {
82 #[inline]
83 #[must_use]
84 pub fn iter(&self) -> Iter<'_> {
85 Iter::from_slice(&self.inner)
86 }
87
88 #[inline]
89 #[must_use]
90 pub fn iter_mut(&mut self) -> IterMut<'_> {
91 IterMut::from_mut_slice(&mut self.inner)
92 }
93
94 #[inline]
95 #[must_use]
96 pub fn bytes(&self) -> Bytes<'_> {
97 Bytes::from_slice(&self.inner)
98 }
99
100 #[inline]
101 #[must_use]
102 pub fn into_iter(self) -> IntoIter {
103 IntoIter::from_vec(self.inner.into_inner())
104 }
105}
106
107impl AsciiString {
109 #[inline]
110 #[must_use]
111 pub fn len(&self) -> usize {
112 self.inner.len()
113 }
114
115 #[inline]
116 pub unsafe fn set_len(&mut self, len: usize) {
117 unsafe {
120 self.inner.set_len(len);
121 }
122 }
123
124 #[inline]
125 #[must_use]
126 pub fn capacity(&self) -> usize {
127 self.inner.capacity()
128 }
129
130 #[inline]
131 pub fn clear(&mut self) {
132 self.inner.clear();
133 }
134
135 #[inline]
136 #[must_use]
137 pub fn is_empty(&self) -> bool {
138 self.inner.is_empty()
139 }
140
141 #[inline]
142 pub fn truncate(&mut self, len: usize) {
143 self.inner.truncate(len);
144 }
145
146 #[inline]
147 #[must_use]
148 pub fn char_len(&self) -> usize {
149 self.len()
150 }
151}
152
153impl AsciiString {
155 #[inline]
156 pub fn reserve(&mut self, additional: usize) {
157 self.inner.reserve(additional);
158 }
159
160 #[inline]
161 pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
162 self.inner.try_reserve(additional)
163 }
164
165 #[inline]
166 pub fn reserve_exact(&mut self, additional: usize) {
167 self.inner.reserve_exact(additional);
168 }
169
170 #[inline]
171 pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
172 self.inner.try_reserve_exact(additional)
173 }
174
175 #[inline]
176 pub fn shrink_to_fit(&mut self) {
177 self.inner.shrink_to_fit();
178 }
179
180 #[inline]
181 pub fn shrink_to(&mut self, min_capacity: usize) {
182 self.inner.shrink_to(min_capacity);
183 }
184}
185
186impl AsciiString {
188 #[inline]
189 #[must_use]
190 pub fn get<I>(&self, index: I) -> Option<&I::Output>
191 where
192 I: SliceIndex<[u8]>,
193 {
194 self.inner.get(index)
195 }
196
197 #[inline]
198 #[must_use]
199 pub fn get_char(&self, index: usize) -> Option<&'_ [u8]> {
200 self.get(index..=index)
201 }
202
203 #[inline]
204 #[must_use]
205 pub fn get_char_slice(&self, range: Range<usize>) -> Option<&'_ [u8]> {
206 let Range { start, end } = range;
207
208 self.inner.get(start..end).or_else(|| {
209 if start > self.inner.len() {
210 None
211 } else if end <= start {
212 Some(&[])
213 } else {
214 self.inner.get(start..)
215 }
216 })
217 }
218
219 #[inline]
220 #[must_use]
221 pub fn get_mut<I>(&mut self, index: I) -> Option<&mut I::Output>
222 where
223 I: SliceIndex<[u8]>,
224 {
225 self.inner.get_mut(index)
226 }
227
228 #[inline]
229 #[must_use]
230 pub unsafe fn get_unchecked<I>(&self, index: I) -> &I::Output
231 where
232 I: SliceIndex<[u8]>,
233 {
234 unsafe { self.inner.get_unchecked(index) }
237 }
238
239 #[inline]
240 #[must_use]
241 pub unsafe fn get_unchecked_mut<I>(&mut self, index: I) -> &mut I::Output
242 where
243 I: SliceIndex<[u8]>,
244 {
245 unsafe { self.inner.get_unchecked_mut(index) }
248 }
249}
250
251impl AsciiString {
253 #[inline]
254 pub fn push_byte(&mut self, byte: u8) {
255 self.inner.push_byte(byte);
256 }
257
258 #[inline]
259 pub fn try_push_codepoint(&mut self, codepoint: i64) -> Result<(), InvalidCodepointError> {
260 if let Ok(byte) = u8::try_from(codepoint) {
261 self.push_byte(byte);
262 Ok(())
263 } else {
264 Err(InvalidCodepointError::codepoint_out_of_range(codepoint))
265 }
266 }
267
268 #[inline]
269 pub fn try_push_int(&mut self, int: i64, enc: &mut Option<Encoding>) -> Result<(), InvalidCodepointError> {
270 match u8::try_from(int) {
271 Ok(byte) if byte.is_ascii() => self.push_byte(byte),
272 Ok(byte) => {
273 self.push_byte(byte);
274 *enc = Some(Encoding::Binary);
275 }
276 Err(_) => return Err(InvalidCodepointError::codepoint_out_of_range(int)),
277 }
278 Ok(())
279 }
280
281 #[inline]
282 pub fn push_char(&mut self, ch: char) {
283 self.inner.push_char(ch);
284 }
285
286 #[inline]
287 pub fn push_str(&mut self, s: &str) {
288 self.inner.push_str(s);
289 }
290
291 #[inline]
292 pub fn extend_from_slice(&mut self, other: &[u8]) {
293 self.inner.extend_from_slice(other);
294 }
295}
296
297impl AsciiString {
299 #[inline]
300 #[must_use]
301 pub fn is_ascii_only(&self) -> bool {
302 self.inner.is_ascii()
303 }
304
305 #[inline]
306 #[must_use]
307 pub fn is_valid_encoding(&self) -> bool {
308 self.is_ascii_only()
309 }
310}
311
312impl AsciiString {
314 #[inline]
315 pub fn make_capitalized(&mut self) -> CaseFoldingEffect {
316 binascii::make_capitalized(self.as_mut_slice())
317 }
318
319 #[inline]
320 pub fn make_lowercase(&mut self) -> CaseFoldingEffect {
321 binascii::make_lowercase(self.as_mut_slice())
322 }
323
324 #[inline]
325 pub fn make_uppercase(&mut self) -> CaseFoldingEffect {
326 binascii::make_uppercase(self.as_mut_slice())
327 }
328
329 #[inline]
330 pub fn make_swapcase(&mut self) -> CaseFoldingEffect {
331 binascii::make_swapcase(self.as_mut_slice())
332 }
333}
334
335impl AsciiString {
336 #[inline]
337 #[must_use]
338 pub fn chr(&self) -> &[u8] {
339 self.inner.get(0..1).unwrap_or_default()
340 }
341
342 #[inline]
343 pub fn ord(&self) -> Result<u32, OrdError> {
344 let byte = self.inner.first().copied().ok_or_else(OrdError::empty_string)?;
345 Ok(u32::from(byte))
346 }
347
348 #[inline]
349 #[must_use]
350 pub fn ends_with(&self, slice: &[u8]) -> bool {
351 self.inner.ends_with(slice)
352 }
353
354 #[inline]
355 pub fn reverse(&mut self) {
356 self.inner.reverse();
357 }
358}
359
360impl AsciiString {
362 #[inline]
363 #[must_use]
364 pub fn index(&self, needle: &[u8], offset: usize) -> Option<usize> {
365 let buf = self.get(offset..)?;
366 let index = buf.find(needle)?;
367 Some(index + offset)
368 }
369
370 #[inline]
371 #[must_use]
372 pub fn rindex(&self, needle: &[u8], offset: usize) -> Option<usize> {
373 let buf = self.get(..=offset).unwrap_or_else(|| self.as_slice());
374 let index = buf.rfind(needle)?;
375 Some(index)
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use alloc::string::String;
382 use alloc::vec::Vec;
383
384 use super::AsciiString;
385 use crate::test::run_arbitrary;
386
387 #[test]
388 fn prop_fuzz_char_len_utf8_contents_ascii_string() {
389 run_arbitrary::<String>(|contents| {
390 let expected = contents.len();
391 let s = AsciiString::from(contents);
392 assert_eq!(s.char_len(), expected);
393 });
394 }
395
396 #[test]
397 fn prop_fuzz_len_utf8_contents_ascii_string() {
398 run_arbitrary::<String>(|contents| {
399 let expected = contents.len();
400 let s = AsciiString::from(contents);
401 assert_eq!(s.len(), expected);
402 });
403 }
404
405 #[test]
406 fn prop_fuzz_char_len_binary_contents_ascii_string() {
407 run_arbitrary::<Vec<u8>>(|contents| {
408 let expected = contents.len();
409 let s = AsciiString::from(contents);
410 assert_eq!(s.char_len(), expected);
411 });
412 }
413
414 #[test]
415 fn prop_fuzz_len_binary_contents_ascii_string() {
416 run_arbitrary::<Vec<u8>>(|contents| {
417 let expected = contents.len();
418 let s = AsciiString::from(contents);
419 assert_eq!(s.len(), expected);
420 });
421 }
422
423 #[test]
424 fn constructs_empty_buffer() {
425 let s = AsciiString::from(Vec::new());
426 assert_eq!(0, s.len());
427 }
428
429 #[test]
430 fn casing_ascii_string_empty() {
431 let mut s = AsciiString::from(b"");
432
433 s.make_capitalized();
434 assert_eq!(s, "");
435
436 s.make_lowercase();
437 assert_eq!(s, "");
438
439 s.make_uppercase();
440 assert_eq!(s, "");
441 }
442
443 #[test]
444 fn casing_ascii_string_ascii() {
445 let lower = AsciiString::from(b"abc");
446 let mid_upper = AsciiString::from(b"aBc");
447 let upper = AsciiString::from(b"ABC");
448 let long = AsciiString::from(b"aBC, 123, ABC, baby you and me girl");
449
450 let capitalize: fn(&AsciiString) -> AsciiString = |value: &AsciiString| {
451 let mut value = value.clone();
452 value.make_capitalized();
453 value
454 };
455 let lowercase: fn(&AsciiString) -> AsciiString = |value: &AsciiString| {
456 let mut value = value.clone();
457 value.make_lowercase();
458 value
459 };
460 let uppercase: fn(&AsciiString) -> AsciiString = |value: &AsciiString| {
461 let mut value = value.clone();
462 value.make_uppercase();
463 value
464 };
465 let swapcase: fn(&AsciiString) -> AsciiString = |value: &AsciiString| {
466 let mut value = value.clone();
467 value.make_swapcase();
468 value
469 };
470
471 assert_eq!(capitalize(&lower), "Abc");
472 assert_eq!(capitalize(&mid_upper), "Abc");
473 assert_eq!(capitalize(&upper), "Abc");
474 assert_eq!(capitalize(&long), "Abc, 123, abc, baby you and me girl");
475
476 assert_eq!(lowercase(&lower), "abc");
477 assert_eq!(lowercase(&mid_upper), "abc");
478 assert_eq!(lowercase(&upper), "abc");
479 assert_eq!(lowercase(&long), "abc, 123, abc, baby you and me girl");
480
481 assert_eq!(uppercase(&lower), "ABC");
482 assert_eq!(uppercase(&mid_upper), "ABC");
483 assert_eq!(uppercase(&upper), "ABC");
484 assert_eq!(uppercase(&long), "ABC, 123, ABC, BABY YOU AND ME GIRL");
485
486 assert_eq!(swapcase(&lower), "ABC");
487 assert_eq!(swapcase(&mid_upper), "AbC");
488 assert_eq!(swapcase(&upper), "abc");
489 assert_eq!(swapcase(&long), "Abc, 123, abc, BABY YOU AND ME GIRL");
490 }
491
492 #[test]
493 fn casing_ascii_string_utf8() {
494 let sharp_s = AsciiString::from("ß");
495 let tomorrow = AsciiString::from("αύριο");
496 let year = AsciiString::from("έτος");
497 let two_byte_chars = AsciiString::from("𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆");
500 let varying_length = AsciiString::from("zȺȾ");
503 let rtl = AsciiString::from("مرحبا الخرشوف");
504
505 let capitalize: fn(&AsciiString) -> AsciiString = |value: &AsciiString| {
506 let mut value = value.clone();
507 value.make_capitalized();
508 value
509 };
510 let lowercase: fn(&AsciiString) -> AsciiString = |value: &AsciiString| {
511 let mut value = value.clone();
512 value.make_lowercase();
513 value
514 };
515 let uppercase: fn(&AsciiString) -> AsciiString = |value: &AsciiString| {
516 let mut value = value.clone();
517 value.make_uppercase();
518 value
519 };
520 let swapcase: fn(&AsciiString) -> AsciiString = |value: &AsciiString| {
521 let mut value = value.clone();
522 value.make_swapcase();
523 value
524 };
525
526 assert_eq!(capitalize(&sharp_s), "ß");
527 assert_eq!(capitalize(&tomorrow), "αύριο");
528 assert_eq!(capitalize(&year), "έτος");
529 assert_eq!(
530 capitalize(&two_byte_chars),
531 "𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆"
532 );
533 assert_eq!(capitalize(&varying_length), "ZȺȾ");
534 assert_eq!(capitalize(&rtl), "مرحبا الخرشوف");
535
536 assert_eq!(lowercase(&sharp_s), "ß");
537 assert_eq!(lowercase(&tomorrow), "αύριο");
538 assert_eq!(lowercase(&year), "έτος");
539 assert_eq!(
540 lowercase(&two_byte_chars),
541 "𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆"
542 );
543 assert_eq!(lowercase(&varying_length), "zȺȾ");
544 assert_eq!(lowercase(&rtl), "مرحبا الخرشوف");
545
546 assert_eq!(uppercase(&sharp_s), "ß");
547 assert_eq!(uppercase(&tomorrow), "αύριο");
548 assert_eq!(uppercase(&year), "έτος");
549 assert_eq!(
550 uppercase(&two_byte_chars),
551 "𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆"
552 );
553 assert_eq!(uppercase(&varying_length), "ZȺȾ");
554 assert_eq!(uppercase(&rtl), "مرحبا الخرشوف");
555
556 assert_eq!(swapcase(&sharp_s), "ß");
557 assert_eq!(swapcase(&tomorrow), "αύριο");
558 assert_eq!(swapcase(&year), "έτος");
559 assert_eq!(
560 swapcase(&two_byte_chars),
561 "𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆"
562 );
563 assert_eq!(swapcase(&varying_length), "ZȺȾ");
564 assert_eq!(swapcase(&rtl), "مرحبا الخرشوف");
565 }
566
567 #[test]
568 fn casing_ascii_string_invalid_utf8() {
569 let mut s = AsciiString::from(b"\xFF\xFE");
570
571 s.make_capitalized();
572 assert_eq!(s, &b"\xFF\xFE"[..]);
573
574 s.make_lowercase();
575 assert_eq!(s, &b"\xFF\xFE"[..]);
576
577 s.make_uppercase();
578 assert_eq!(s, &b"\xFF\xFE"[..]);
579
580 s.make_swapcase();
581 assert_eq!(s, &b"\xFF\xFE"[..]);
582 }
583
584 #[test]
585 fn casing_ascii_string_unicode_replacement_character() {
586 let mut s = AsciiString::from("�");
587
588 s.make_capitalized();
589 assert_eq!(s, "�");
590
591 s.make_lowercase();
592 assert_eq!(s, "�");
593
594 s.make_uppercase();
595 assert_eq!(s, "�");
596
597 s.make_swapcase();
598 assert_eq!(s, "�");
599 }
600
601 #[test]
602 fn get_char_slice_valid_range() {
603 let s = AsciiString::from("abc");
604 assert_eq!(s.get_char_slice(0..0), Some(&b""[..]));
605 assert_eq!(s.get_char_slice(0..1), Some(&b"a"[..]));
606 assert_eq!(s.get_char_slice(0..2), Some(&b"ab"[..]));
607 assert_eq!(s.get_char_slice(0..3), Some(&b"abc"[..]));
608 assert_eq!(s.get_char_slice(0..4), Some(&b"abc"[..]));
609 assert_eq!(s.get_char_slice(1..1), Some(&b""[..]));
610 assert_eq!(s.get_char_slice(1..2), Some(&b"b"[..]));
611 assert_eq!(s.get_char_slice(1..3), Some(&b"bc"[..]));
612 }
613
614 #[test]
615 #[expect(clippy::reversed_empty_ranges, reason = "testing behavior of reversed ranges")]
616 fn get_char_slice_invalid_range() {
617 let s = AsciiString::from("abc");
618 assert_eq!(s.get_char_slice(4..5), None);
619 assert_eq!(s.get_char_slice(4..1), None);
620 assert_eq!(s.get_char_slice(3..1), Some(&b""[..]));
621 assert_eq!(s.get_char_slice(2..1), Some(&b""[..]));
622 }
623
624 #[test]
625 fn index_with_default_offset() {
626 let s = AsciiString::from(b"foo");
627 assert_eq!(s.index("f".as_bytes(), 0), Some(0));
628 assert_eq!(s.index("o".as_bytes(), 0), Some(1));
629 assert_eq!(s.index("oo".as_bytes(), 0), Some(1));
630 assert_eq!(s.index("ooo".as_bytes(), 0), None);
631 }
632
633 #[test]
634 fn index_with_different_offset() {
635 let s = AsciiString::from(b"foo");
636 assert_eq!(s.index("o".as_bytes(), 1), Some(1));
637 assert_eq!(s.index("o".as_bytes(), 2), Some(2));
638 assert_eq!(s.index("o".as_bytes(), 3), None);
639 }
640
641 #[test]
642 fn index_offset_no_overflow() {
643 let s = AsciiString::from(b"foo");
644 assert_eq!(s.index("o".as_bytes(), usize::MAX), None);
645 }
646
647 #[test]
648 fn index_empties() {
649 let s = AsciiString::from("");
658 assert_eq!(s.index(b"", 0), Some(0));
659
660 assert_eq!(s.index(b"a", 0), None);
661
662 let s = AsciiString::from("a");
663 assert_eq!(s.index(b"", 0), Some(0));
664 }
665
666 #[test]
667 fn rindex_with_default_offset() {
668 let s = AsciiString::from(b"foo");
669 assert_eq!(s.rindex("f".as_bytes(), 2), Some(0));
670 assert_eq!(s.rindex("o".as_bytes(), 2), Some(2));
671 assert_eq!(s.rindex("oo".as_bytes(), 2), Some(1));
672 assert_eq!(s.rindex("ooo".as_bytes(), 2), None);
673 }
674
675 #[test]
676 fn rindex_with_different_offset() {
677 let s = AsciiString::from(b"foo");
678 assert_eq!(s.rindex("o".as_bytes(), 3), Some(2));
679 assert_eq!(s.rindex("o".as_bytes(), 2), Some(2));
680 assert_eq!(s.rindex("o".as_bytes(), 1), Some(1));
681 assert_eq!(s.rindex("o".as_bytes(), 0), None);
682 }
683
684 #[test]
685 fn rindex_offset_no_overflow() {
686 let s = AsciiString::from(b"foo");
687 assert_eq!(s.rindex("o".as_bytes(), usize::MAX), Some(2));
688 }
689
690 #[test]
691 fn rindex_empties() {
692 let s = AsciiString::from("");
701 assert_eq!(s.rindex(b"", usize::MAX), Some(0));
702 assert_eq!(s.rindex(b"", 1), Some(0));
703 assert_eq!(s.rindex(b"", 0), Some(0));
704
705 assert_eq!(s.rindex(b"a", usize::MAX), None);
706 assert_eq!(s.rindex(b"a", 1), None);
707 assert_eq!(s.rindex(b"a", 0), None);
708
709 let s = AsciiString::from("a");
710 assert_eq!(s.rindex(b"", usize::MAX), Some(1));
711 assert_eq!(s.rindex(b"", 1), Some(1));
712 }
713}