1use std::borrow::Cow;
22use std::marker::PhantomData;
23use ::ScanError;
24
25pub trait IntoScanCursor<'a>: Sized {
31 type Output: 'a + ScanCursor<'a>;
35
36 fn into_scan_cursor(self) -> Self::Output;
40}
41
42impl<'a, T> IntoScanCursor<'a> for T where T: 'a + ScanCursor<'a> {
43 type Output = Self;
44 fn into_scan_cursor(self) -> Self::Output {
45 self
46 }
47}
48
49impl<'a> IntoScanCursor<'a> for &'a str {
50 type Output = StrCursor<'a>;
51 fn into_scan_cursor(self) -> Self::Output {
52 StrCursor::new(self)
53 }
54}
55
56impl<'a> IntoScanCursor<'a> for &'a String {
57 type Output = StrCursor<'a>;
58 fn into_scan_cursor(self) -> Self::Output {
59 StrCursor::new(self)
60 }
61}
62
63impl<'a> IntoScanCursor<'a> for &'a Cow<'a, str> {
64 type Output = StrCursor<'a>;
65 fn into_scan_cursor(self) -> Self::Output {
66 StrCursor::new(self)
67 }
68}
69
70pub trait ScanCursor<'a>: 'a + Sized + Clone {
74 type ScanInput: ScanInput<'a>;
78
79 fn try_end(self) -> Result<(), (ScanError, Self)>;
83
84 fn try_scan<F, Out>(self, f: F) -> Result<(Out, Self), (ScanError, Self)>
90 where F: FnOnce(Self::ScanInput) -> Result<(Out, usize), ScanError>;
91
92 fn try_scan_raw<F, Out>(self, f: F) -> Result<(Out, Self), (ScanError, Self)>
96 where F: FnOnce(Self::ScanInput) -> Result<(Out, usize), ScanError>;
97
98 fn try_match_literal(self, lit: &str) -> Result<Self, (ScanError, Self)>;
104
105 fn as_str(self) -> &'a str;
109
110 fn offset(&self) -> usize;
114}
115
116pub trait ScanInput<'a>: 'a + Sized + Clone {
120 type ScanCursor: ScanCursor<'a>;
124
125 type StrCompare: StrCompare;
129
130 fn as_str(&self) -> &'a str;
134
135 fn from_subslice(&self, subslice: &'a str) -> Self;
141
142 fn to_cursor(&self) -> Self::ScanCursor;
146}
147
148#[derive(Debug)]
154pub struct StrCursor<'a, Cmp=ExactCompare, Space=IgnoreSpace, Word=Wordish>
155where
156 Cmp: StrCompare,
157 Space: SkipSpace,
158 Word: SliceWord,
159{
160 offset: usize,
161 slice: &'a str,
162 _marker: PhantomData<(Cmp, Space, Word)>,
163}
164
165impl<'a, Cmp, Space, Word>
169Copy for StrCursor<'a, Cmp, Space, Word>
170where
171 Cmp: StrCompare,
172 Space: SkipSpace,
173 Word: SliceWord,
174{}
175
176impl<'a, Cmp, Space, Word>
177Clone for StrCursor<'a, Cmp, Space, Word>
178where
179 Cmp: StrCompare,
180 Space: SkipSpace,
181 Word: SliceWord,
182{
183 fn clone(&self) -> Self {
184 *self
185 }
186}
187
188impl<'a, Cmp, Space, Word>
189StrCursor<'a, Cmp, Space, Word>
190where
191 Cmp: StrCompare,
192 Space: SkipSpace,
193 Word: SliceWord,
194{
195 pub fn new(slice: &'a str) -> Self {
201 StrCursor {
202 offset: 0,
203 slice: slice,
204 _marker: PhantomData,
205 }
206 }
207
208 fn advance_by(self, bytes: usize) -> Self {
212 StrCursor {
213 offset: self.offset + bytes,
214 slice: &self.slice[bytes..],
215 _marker: PhantomData,
216 }
217 }
218
219 fn offset(self) -> usize {
223 self.offset
224 }
225}
226
227impl<'a, Cmp, Space, Word>
228ScanCursor<'a> for StrCursor<'a, Cmp, Space, Word>
229where
230 Cmp: StrCompare,
231 Space: SkipSpace,
232 Word: SliceWord,
233{
234 type ScanInput = Self;
235
236 fn try_end(self) -> Result<(), (ScanError, Self)> {
237 if Space::skip_space(self.slice) == self.slice.len() {
238 Ok(())
239 } else {
240 Err((ScanError::expected_end().add_offset(self.offset()), self))
241 }
242 }
243
244 fn try_scan<F, Out>(self, f: F) -> Result<(Out, Self), (ScanError, Self)>
245 where F: FnOnce(Self::ScanInput) -> Result<(Out, usize), ScanError> {
246 let tmp_off = Space::skip_space(self.slice);
247 let tmp = self.advance_by(tmp_off);
248 match f(tmp) {
249 Ok((out, off)) => Ok((out, tmp.advance_by(off))),
250 Err(err) => Err((err.add_offset(tmp.offset()), self)),
251 }
252 }
253
254 fn try_scan_raw<F, Out>(self, f: F) -> Result<(Out, Self), (ScanError, Self)>
255 where F: FnOnce(Self::ScanInput) -> Result<(Out, usize), ScanError> {
256 match f(self) {
257 Ok((out, off)) => Ok((out, self.advance_by(off))),
258 Err(err) => Err((err.add_offset(self.offset()), self)),
259 }
260 }
261
262 fn try_match_literal(self, lit: &str) -> Result<Self, (ScanError, Self)> {
263 let mut tmp_off = Space::skip_space(self.slice);
264 let mut tmp = &self.slice[tmp_off..];
265 let mut lit = lit;
266
267 while lit.len() > 0 {
268 match Space::match_spaces(tmp, lit) {
270 Ok((a, b)) => {
271 tmp = &tmp[a..];
272 tmp_off += a;
273 lit = &lit[b..];
274 },
275 Err(off) => {
276 return Err((
277 ScanError::literal_mismatch()
278 .add_offset(self.offset() + tmp_off + off),
279 self
280 ));
281 },
282 }
283
284 if lit.len() == 0 { break; }
285
286 let lit_word = match Word::slice_word(lit) {
288 Some(0) | None => panic!("literal {:?} begins with a non-space, non-word", lit),
289 Some(b) => &lit[..b],
290 };
291 let tmp_word = match Word::slice_word(tmp) {
292 Some(b) => &tmp[..b],
293 None => return Err((
294 ScanError::literal_mismatch()
295 .add_offset(self.offset() + tmp_off),
296 self
297 )),
298 };
299
300 if !Cmp::compare(tmp_word, lit_word) {
301 return Err((
302 ScanError::literal_mismatch()
303 .add_offset(self.offset() + tmp_off),
304 self
305 ));
306 }
307
308 tmp = &tmp[tmp_word.len()..];
309 tmp_off += tmp_word.len();
310 lit = &lit[lit_word.len()..];
311 }
312
313 Ok(self.advance_by(tmp_off))
314 }
315
316 fn as_str(self) -> &'a str {
317 self.slice
318 }
319
320 fn offset(&self) -> usize {
321 self.offset
322 }
323}
324
325impl<'a, Cmp, Space, Word>
326ScanInput<'a> for StrCursor<'a, Cmp, Space, Word>
327where
328 Cmp: StrCompare,
329 Space: SkipSpace,
330 Word: SliceWord,
331{
332 type ScanCursor = Self;
333 type StrCompare = Cmp;
334
335 fn as_str(&self) -> &'a str {
336 self.slice
337 }
338
339 fn from_subslice(&self, subslice: &'a str) -> Self {
340 use ::util::StrUtil;
341 let offset = self.as_str().subslice_offset_stable(subslice)
342 .expect("called `StrCursor::from_subslice` with disjoint subslice");
343
344 StrCursor {
345 offset: self.offset + offset,
346 slice: subslice,
347 _marker: PhantomData,
348 }
349 }
350
351 fn to_cursor(&self) -> Self::ScanCursor {
352 StrCursor::new(self.slice)
356 }
357}
358
359impl<'a> ScanInput<'a> for &'a str {
365 type ScanCursor = StrCursor<'a>;
366 type StrCompare = ExactCompare;
367
368 fn as_str(&self) -> &'a str {
369 *self
370 }
371
372 fn from_subslice(&self, subslice: &'a str) -> Self {
373 subslice
374 }
375
376 fn to_cursor(&self) -> Self::ScanCursor {
377 self.into_scan_cursor()
378 }
379}
380
381fn skip_space(s: &str) -> (&str, usize) {
385 let off = s.char_indices()
386 .take_while(|&(_, c)| c.is_whitespace())
387 .map(|(i, c)| i + c.len_utf8())
388 .last()
389 .unwrap_or(0);
390 (&s[off..], off)
391}
392
393pub trait SkipSpace: 'static {
397 fn match_spaces(a: &str, b: &str) -> Result<(usize, usize), usize>;
405
406 fn skip_space(a: &str) -> usize;
410}
411
412#[derive(Debug)]
416pub enum ExactSpace {}
417
418impl SkipSpace for ExactSpace {
419 fn match_spaces(a: &str, b: &str) -> Result<(usize, usize), usize> {
420 let mut acs = a.char_indices();
421 let mut bcs = b.char_indices();
422 let (mut last_ai, mut last_bi) = (0, 0);
423 while let (Some((ai, ac)), Some((bi, bc))) = (acs.next(), bcs.next()) {
424 if !ac.is_whitespace() {
425 return Ok((ai, bi));
426 } else if ac != bc {
427 return Err(ai);
428 } else {
429 last_ai = ai + ac.len_utf8();
430 last_bi = bi + ac.len_utf8();
431 }
432 }
433 Ok((last_ai, last_bi))
434 }
435
436 fn skip_space(_: &str) -> usize {
437 0
438 }
439}
440
441#[cfg(test)]
442#[test]
443fn test_exact_space() {
444 use self::ExactSpace as ES;
445
446 assert_eq!(ES::match_spaces("", ""), Ok((0, 0)));
447 assert_eq!(ES::match_spaces(" ", " "), Ok((1, 1)));
448 assert_eq!(ES::match_spaces(" x", " x"), Ok((1, 1)));
449 assert_eq!(ES::match_spaces(" ", " x"), Ok((1, 1)));
450 assert_eq!(ES::match_spaces(" x", " "), Ok((1, 1)));
451 assert_eq!(ES::match_spaces(" \t ", " "), Err(1));
452}
453
454#[derive(Debug)]
458pub enum FuzzySpace {}
459
460impl SkipSpace for FuzzySpace {
461 fn match_spaces(inp: &str, pat: &str) -> Result<(usize, usize), usize> {
462 let (_, a_off) = skip_space(inp);
463 let (_, b_off) = skip_space(pat);
464
465 match (a_off, b_off) {
466 (0, 0) => Ok((0, 0)),
467 (a, b) if a != 0 && b != 0 => Ok((a, b)),
468 (_, _) => Err(0),
469 }
470 }
471
472 fn skip_space(_: &str) -> usize {
473 0
474 }
475}
476
477#[cfg(test)]
478#[test]
479fn test_fuzzy_space() {
480 use self::FuzzySpace as FS;
481
482 assert_eq!(FS::match_spaces("x", "x"), Ok((0, 0)));
483 assert_eq!(FS::match_spaces(" x", " x"), Ok((1, 1)));
484 assert_eq!(FS::match_spaces(" x", " x"), Ok((2, 1)));
485 assert_eq!(FS::match_spaces(" x", " x"), Ok((1, 2)));
486 assert_eq!(FS::match_spaces("\tx", " x"), Ok((1, 1)));
487 assert_eq!(FS::match_spaces(" x", "\tx"), Ok((1, 1)));
488 assert_eq!(FS::match_spaces("x", " x"), Err(0));
489 assert_eq!(FS::match_spaces(" x", "x"), Err(0));
490}
491
492#[derive(Debug)]
496pub enum IgnoreNonLine {}
497
498impl SkipSpace for IgnoreNonLine {
499 fn match_spaces(a: &str, b: &str) -> Result<(usize, usize), usize> {
500 let a_off = skip_space_non_line(a);
501 let b_off = skip_space_non_line(b);
502 Ok((a_off, b_off))
503 }
504
505 fn skip_space(s: &str) -> usize {
506 skip_space_non_line(s)
507 }
508}
509
510fn skip_space_non_line(s: &str) -> usize {
511 s.char_indices()
512 .take_while(|&(_, c)| c.is_whitespace()
513 && c != '\r' && c != '\n')
514 .last()
515 .map(|(i, c)| i + c.len_utf8())
516 .unwrap_or(0)
517}
518
519#[derive(Debug)]
523pub enum IgnoreSpace {}
524
525impl SkipSpace for IgnoreSpace {
526 fn match_spaces(a: &str, b: &str) -> Result<(usize, usize), usize> {
527 let (_, a_off) = skip_space(a);
528 let (_, b_off) = skip_space(b);
529 Ok((a_off, b_off))
530 }
531
532 fn skip_space(s: &str) -> usize {
533 s.char_indices()
534 .take_while(|&(_, c)| c.is_whitespace())
535 .map(|(i, c)| i + c.len_utf8())
536 .last()
537 .unwrap_or(0)
538 }
539}
540
541pub trait SliceWord: 'static {
545 fn slice_word(s: &str) -> Option<usize>;
549}
550
551#[derive(Debug)]
555pub enum NonSpace {}
556
557impl SliceWord for NonSpace {
558 fn slice_word(s: &str) -> Option<usize> {
559 slice_non_space(s)
560 }
561}
562
563#[derive(Debug)]
567pub enum Wordish {}
568
569impl SliceWord for Wordish {
570 fn slice_word(s: &str) -> Option<usize> {
571 slice_wordish(s)
572 }
573}
574
575pub trait StrCompare: 'static {
581 fn compare(a: &str, b: &str) -> bool;
585}
586
587#[derive(Debug)]
593pub enum ExactCompare {}
594
595impl StrCompare for ExactCompare {
596 fn compare(a: &str, b: &str) -> bool {
597 a == b
598 }
599}
600
601#[derive(Debug)]
607pub enum IgnoreCase {}
608
609impl StrCompare for IgnoreCase {
610 fn compare(a: &str, b: &str) -> bool {
611 let mut acs = a.chars().flat_map(char::to_lowercase);
612 let mut bcs = b.chars().flat_map(char::to_lowercase);
613 loop {
614 match (acs.next(), bcs.next()) {
615 (Some(a), Some(b)) if a == b => (),
616 (None, None) => return true,
617 _ => return false
618 }
619 }
620 }
621}
622
623#[cfg(test)]
624#[test]
625fn test_ignore_case() {
626 use self::IgnoreCase as IC;
627
628 assert_eq!(IC::compare("hi", "hi"), true);
629 assert_eq!(IC::compare("Hi", "hI"), true);
630 assert_eq!(IC::compare("hI", "Hi"), true);
631 assert_eq!(IC::compare("ẞß", "ßẞ"), true);
632 assert_eq!(IC::compare("ßẞ", "ẞß"), true);
633}
634
635#[cfg(feature="unicode-normalization")]
643#[derive(Debug)]
644pub enum IgnoreCaseNormalized {}
645
646#[cfg(feature="unicode-normalization")]
647impl StrCompare for IgnoreCaseNormalized {
648 fn compare(a: &str, b: &str) -> bool {
649 use unicode_normalization::UnicodeNormalization;
650
651 let mut acs = a.nfd().flat_map(char::to_lowercase);
652 let mut bcs = b.nfd().flat_map(char::to_lowercase);
653 loop {
654 match (acs.next(), bcs.next()) {
655 (Some(a), Some(b)) if a == b => (),
656 (None, None) => return true,
657 _ => return false
658 }
659 }
660 }
661}
662
663#[cfg(feature="unicode-normalization")]
664#[cfg(test)]
665#[test]
666fn test_ignore_case_normalized() {
667 use self::IgnoreCaseNormalized as ICN;
668
669 assert_eq!(ICN::compare("hi", "hi"), true);
670 assert_eq!(ICN::compare("Hi", "hI"), true);
671 assert_eq!(ICN::compare("hI", "Hi"), true);
672 assert_eq!(ICN::compare("café", "cafe\u{301}"), true);
673 assert_eq!(ICN::compare("cafe\u{301}", "café"), true);
674 assert_eq!(ICN::compare("CafÉ", "CafE\u{301}"), true);
675 assert_eq!(ICN::compare("CAFÉ", "cafe\u{301}"), true);
676}
677
678#[derive(Debug)]
686pub enum IgnoreAsciiCase {}
687
688impl StrCompare for IgnoreAsciiCase {
689 fn compare(a: &str, b: &str) -> bool {
690 use std::ascii::AsciiExt;
691 a.eq_ignore_ascii_case(b)
692 }
693}
694
695#[cfg(feature="unicode-normalization")]
701#[derive(Debug)]
702pub enum Normalized {}
703
704#[cfg(feature="unicode-normalization")]
705impl StrCompare for Normalized {
706 fn compare(a: &str, b: &str) -> bool {
707 use unicode_normalization::UnicodeNormalization;
708
709 let mut acs = a.nfd();
710 let mut bcs = b.nfd();
711 loop {
712 match (acs.next(), bcs.next()) {
713 (Some(a), Some(b)) if a == b => (),
714 (None, None) => return true,
715 _ => return false
716 }
717 }
718 }
719}
720
721#[cfg(feature="unicode-normalization")]
722#[cfg(test)]
723#[test]
724fn test_normalized() {
725 use self::Normalized as N;
726
727 assert_eq!(N::compare("hi", "hi"), true);
728 assert_eq!(N::compare("café", "cafe\u{301}"), true);
729 assert_eq!(N::compare("cafe\u{301}", "café"), true);
730}
731
732fn slice_non_space(s: &str) -> Option<usize> {
733 use ::util::TableUtil;
734 use ::unicode::property::White_Space_table as WS;
735
736 s.char_indices()
737 .take_while(|&(_, c)| !WS.span_table_contains(&c))
738 .map(|(i, c)| i + c.len_utf8())
739 .last()
740}
741
742fn slice_wordish(s: &str) -> Option<usize> {
743 use ::util::TableUtil;
744 use ::unicode::regex::PERLW;
745
746 let word_len = s.char_indices()
747 .take_while(|&(_, c)| PERLW.span_table_contains(&c))
748 .map(|(i, c)| i + c.len_utf8())
749 .last();
750
751 match word_len {
752 Some(n) => Some(n),
753 None => s.chars().next().map(|c| c.len_utf8()),
754 }
755}