1#![deny(missing_docs)]
59
60use core::fmt::{Debug, Display};
61use core::ops::{Bound, RangeBounds};
62use core::str::FromStr;
63
64pub trait IndexedStr:
80 Display + Debug + PartialEq<IndexedString> + for<'a> PartialEq<IndexedSlice<'a>>
81{
82 fn as_str(&self) -> &str;
88
89 fn as_slice(&self) -> IndexedSlice;
91
92 fn len(&self) -> usize;
94
95 fn byte_len(&self) -> usize;
98
99 fn is_empty(&self) -> bool {
101 self.len() == 0
102 }
103
104 fn char_at(&self, index: usize) -> Option<char>;
106
107 fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice;
112
113 fn chars(&self) -> &[char];
115
116 fn to_indexed_string(&self) -> IndexedString;
118
119 fn to_lowercase(&self) -> IndexedString {
121 self.as_str().to_lowercase().into()
122 }
123
124 fn to_uppercase(&self) -> IndexedString {
126 self.as_str().to_uppercase().into()
127 }
128
129 fn starts_with<S: AsRef<str>>(&self, s: S) -> bool {
131 self.as_str().starts_with(s.as_ref())
132 }
133
134 fn ends_with<S: AsRef<str>>(&self, s: S) -> bool {
136 self.as_str().ends_with(s.as_ref())
137 }
138
139 fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>
141 where
142 F: FromStr,
143 {
144 self.as_str().parse()
145 }
146
147 fn lines(&self) -> IndexedLines;
149}
150
151#[derive(Clone, Debug, Eq, Hash)]
155pub struct IndexedString {
156 chars: Vec<char>,
157 offsets: Vec<usize>,
158 string: String,
159}
160
161impl IndexedStr for IndexedString {
162 fn as_str(&self) -> &str {
163 &self.string
164 }
165
166 fn char_at(&self, index: usize) -> Option<char> {
167 self.chars.get(index).copied()
168 }
169
170 fn chars(&self) -> &[char] {
171 &self.chars[..]
172 }
173
174 fn len(&self) -> usize {
175 self.chars.len()
176 }
177
178 fn byte_len(&self) -> usize {
179 self.string.len()
180 }
181
182 fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice {
183 let start = match range.start_bound() {
184 Bound::Included(&start) => start,
185 Bound::Excluded(&start) => start + 1,
186 Bound::Unbounded => 0,
187 };
188 let end = match range.end_bound() {
189 Bound::Included(&end) => end + 1,
190 Bound::Excluded(&end) => end,
191 Bound::Unbounded => self.chars.len(),
192 };
193 let start = if start > self.chars.len() {
194 self.chars.len()
195 } else {
196 start
197 };
198 let end = if end > self.chars.len() {
199 self.chars.len()
200 } else {
201 end
202 };
203
204 IndexedSlice {
205 source: self,
206 start,
207 end,
208 }
209 }
210
211 fn to_indexed_string(&self) -> IndexedString {
212 self.clone()
213 }
214
215 fn as_slice(&self) -> IndexedSlice {
216 IndexedSlice {
217 source: self,
218 start: 0,
219 end: self.chars.len(),
220 }
221 }
222
223 fn lines(&self) -> IndexedLines {
224 IndexedLines {
225 source: self,
226 start: 0,
227 }
228 }
229}
230
231impl IndexedString {
232 pub fn from_str(s: impl Display) -> Self {
234 IndexedString::from_string(s.to_string())
235 }
236
237 pub fn from_string(s: String) -> Self {
240 IndexedString {
241 chars: s.chars().collect(),
242 offsets: s.char_indices().map(|(i, _)| i).collect(),
243 string: s,
244 }
245 }
246
247 pub fn from_chars(chars: impl Iterator<Item = char>) -> Self {
249 let chars: Vec<char> = chars.collect();
250 let offsets: Vec<usize> = chars
251 .iter()
252 .scan(0, |acc, &c| {
253 let offset = *acc;
254 *acc += c.len_utf8();
255 Some(offset)
256 })
257 .collect();
258 let string: String = chars.iter().collect();
259 IndexedString {
260 chars,
261 offsets,
262 string,
263 }
264 }
265}
266
267impl AsRef<str> for IndexedString {
268 fn as_ref(&self) -> &str {
269 &self.string
270 }
271}
272
273impl Display for IndexedString {
274 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
275 write!(f, "{}", self.string)
276 }
277}
278
279impl<S: AsRef<str>> PartialEq<S> for IndexedString {
280 fn eq(&self, other: &S) -> bool {
281 self.string == other.as_ref()
282 }
283}
284
285#[derive(Eq, Debug, Clone)]
289pub struct IndexedSlice<'a> {
290 source: &'a IndexedString,
291 start: usize,
292 end: usize,
293}
294
295impl IndexedStr for IndexedSlice<'_> {
296 fn as_str(&self) -> &str {
297 if self.start >= self.source.offsets.len()
298 || self.end > self.source.offsets.len()
299 || self.start > self.end
300 {
301 return "";
302 }
303
304 let start_byte = self.source.offsets[self.start];
305 let end_byte = if self.end == self.source.offsets.len() {
306 self.source.string.len()
307 } else {
308 self.source.offsets[self.end]
309 };
310
311 &self.source.string[start_byte..end_byte]
312 }
313
314 fn len(&self) -> usize {
315 self.end - self.start
316 }
317
318 fn byte_len(&self) -> usize {
319 self.source.offsets[self.end] - self.source.offsets[self.start]
320 }
321
322 fn char_at(&self, index: usize) -> Option<char> {
323 self.source.char_at(self.start + index)
324 }
325
326 fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice {
327 let start = match range.start_bound() {
328 Bound::Included(&start) => start,
329 Bound::Excluded(&start) => start + 1,
330 Bound::Unbounded => 0,
331 };
332 let end = match range.end_bound() {
333 Bound::Included(&end) => end + 1,
334 Bound::Excluded(&end) => end,
335 Bound::Unbounded => self.len(),
336 };
337 let start = if start > self.len() {
338 self.len()
339 } else {
340 start
341 };
342 let end = if end > self.len() { self.len() } else { end };
343
344 IndexedSlice {
345 source: self.source,
346 start: self.start + start,
347 end: self.start + end,
348 }
349 }
350
351 fn chars(&self) -> &[char] {
352 &self.source.chars[self.start..self.end]
353 }
354
355 fn to_indexed_string(&self) -> IndexedString {
356 IndexedString::from_chars(self.chars().iter().copied())
357 }
358
359 fn as_slice(&self) -> IndexedSlice {
360 self.clone()
361 }
362
363 fn lines(&self) -> IndexedLines {
364 IndexedLines {
365 source: self.source,
366 start: self.start,
367 }
368 }
369}
370
371impl<S: AsRef<str>> PartialEq<S> for IndexedSlice<'_> {
372 fn eq(&self, other: &S) -> bool {
373 self.as_str() == other.as_ref()
374 }
375}
376
377impl AsRef<str> for IndexedSlice<'_> {
378 fn as_ref(&self) -> &str {
379 self.as_str()
380 }
381}
382
383impl<'a> From<&'a IndexedString> for IndexedSlice<'a> {
384 fn from(s: &'a IndexedString) -> Self {
385 IndexedSlice {
386 source: s,
387 start: 0,
388 end: s.chars.len(),
389 }
390 }
391}
392
393impl<'a> From<IndexedSlice<'a>> for IndexedString {
394 fn from(s: IndexedSlice<'a>) -> Self {
395 s.to_indexed_string()
396 }
397}
398
399impl From<String> for IndexedString {
400 fn from(s: String) -> Self {
401 IndexedString::from_string(s)
402 }
403}
404
405impl From<&str> for IndexedString {
406 fn from(s: &str) -> Self {
407 IndexedString::from_str(s)
408 }
409}
410
411impl From<&String> for IndexedString {
412 fn from(s: &String) -> Self {
413 IndexedString::from_string(s.clone())
414 }
415}
416
417impl Display for IndexedSlice<'_> {
418 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
419 write!(f, "{}", self.as_str())
420 }
421}
422
423impl IndexedStr for &IndexedString {
424 fn as_str(&self) -> &str {
425 (*self).as_str()
426 }
427
428 fn as_slice(&self) -> IndexedSlice {
429 (*self).as_slice()
430 }
431
432 fn len(&self) -> usize {
433 (*self).len()
434 }
435
436 fn byte_len(&self) -> usize {
437 (*self).byte_len()
438 }
439
440 fn char_at(&self, index: usize) -> Option<char> {
441 (*self).char_at(index)
442 }
443
444 fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice {
445 (*self).slice(range)
446 }
447
448 fn chars(&self) -> &[char] {
449 (*self).chars()
450 }
451
452 fn to_indexed_string(&self) -> IndexedString {
453 (*self).to_indexed_string()
454 }
455
456 fn lines(&self) -> IndexedLines {
457 (*self).lines()
458 }
459}
460
461impl PartialEq<IndexedString> for &IndexedString {
462 fn eq(&self, other: &IndexedString) -> bool {
463 self.as_str() == other.as_str()
464 }
465}
466
467impl PartialEq<IndexedSlice<'_>> for &IndexedString {
468 fn eq(&self, other: &IndexedSlice) -> bool {
469 self.as_str() == other.as_str()
470 }
471}
472
473impl IndexedStr for &IndexedSlice<'_> {
474 fn as_str(&self) -> &str {
475 (*self).as_str()
476 }
477
478 fn as_slice(&self) -> IndexedSlice {
479 (*self).as_slice()
480 }
481
482 fn len(&self) -> usize {
483 (*self).len()
484 }
485
486 fn byte_len(&self) -> usize {
487 (*self).byte_len()
488 }
489
490 fn char_at(&self, index: usize) -> Option<char> {
491 (*self).char_at(index)
492 }
493
494 fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice {
495 (*self).slice(range)
496 }
497
498 fn chars(&self) -> &[char] {
499 (*self).chars()
500 }
501
502 fn to_indexed_string(&self) -> IndexedString {
503 (*self).to_indexed_string()
504 }
505
506 fn lines(&self) -> IndexedLines {
507 (*self).lines()
508 }
509}
510
511impl PartialEq<IndexedString> for &IndexedSlice<'_> {
512 fn eq(&self, other: &IndexedString) -> bool {
513 self.as_str() == other.as_str()
514 }
515}
516
517impl PartialEq<IndexedSlice<'_>> for &IndexedSlice<'_> {
518 fn eq(&self, other: &IndexedSlice) -> bool {
519 self.as_str() == other.as_str()
520 }
521}
522
523impl PartialEq<IndexedSlice<'_>> for &str {
524 fn eq(&self, other: &IndexedSlice) -> bool {
525 other.as_str() == *self
526 }
527}
528
529impl PartialEq<IndexedSlice<'_>> for String {
530 fn eq(&self, other: &IndexedSlice) -> bool {
531 other.as_str() == *self
532 }
533}
534
535impl PartialEq<IndexedString> for &str {
536 fn eq(&self, other: &IndexedString) -> bool {
537 other.as_str() == *self
538 }
539}
540
541impl PartialEq<IndexedString> for String {
542 fn eq(&self, other: &IndexedString) -> bool {
543 other.as_str() == *self
544 }
545}
546
547pub struct IndexedLines<'a> {
549 source: &'a IndexedString,
550 start: usize,
551}
552
553impl<'a> Iterator for IndexedLines<'a> {
554 type Item = IndexedSlice<'a>;
555
556 fn next(&mut self) -> Option<Self::Item> {
557 if self.start > self.source.chars.len() {
558 return None;
559 }
560
561 if self.start == self.source.chars.len() {
562 self.start += 1; return Some(self.source.slice(self.start - 1..self.start - 1));
564 }
565
566 let mut end = self.start;
567 while end < self.source.chars.len() {
568 if self.source.chars[end] == '\n' {
569 let line = self.source.slice(self.start..end);
570 self.start = end + 1; return Some(line);
572 }
573 end += 1;
574 }
575
576 if self.start <= self.source.chars.len() {
577 let line = self.source.slice(self.start..self.source.chars.len());
578 self.start = self.source.chars.len() + 1; return Some(line);
580 }
581
582 None
583 }
584}