1use crate::prelude::*;
10use alloc::string::ToString;
11
12use super::*;
13
14pub trait Char: Copy + PartialEq + Sealed {
19 fn is_inline_whitespace(&self) -> bool;
21
22 fn is_whitespace(&self) -> bool;
24
25 fn is_newline(&self) -> bool;
27
28 fn digit_zero() -> Self;
30
31 fn is_digit(&self, radix: u32) -> bool;
33
34 fn is_ident_start(&self) -> bool;
36
37 fn is_ident_continue(&self) -> bool;
39
40 fn to_ascii(&self) -> Option<u8>;
42}
43
44impl Sealed for &Grapheme {}
45impl Char for &Grapheme {
46 fn is_inline_whitespace(&self) -> bool {
47 self.as_str() == " " || self.as_str() == "\t"
48 }
49
50 fn is_whitespace(&self) -> bool {
51 let mut iter = self.as_str().chars();
52 iter.all(unicode::is_whitespace)
53 }
54
55 fn is_newline(&self) -> bool {
56 [
57 "\r\n", "\n", "\r", "\x0B", "\x0C", "\u{0085}", "\u{2028}", "\u{2029}", ]
66 .as_slice()
67 .contains(&self.as_str())
68 }
69
70 fn digit_zero() -> Self {
71 Grapheme::digit_zero()
72 }
73
74 fn is_digit(&self, radix: u32) -> bool {
75 let mut iter = self.as_str().chars();
76 match (iter.next(), iter.next()) {
77 (Some(i), None) => i.is_digit(radix),
78 _ => false,
79 }
80 }
81
82 fn to_ascii(&self) -> Option<u8> {
83 let mut iter = self.as_bytes().iter();
84 match (iter.next(), iter.next()) {
85 (Some(i), None) if i.is_ascii() => Some(*i),
86 _ => None,
87 }
88 }
89
90 fn is_ident_start(&self) -> bool {
91 let (first, rest) = self.split();
92 let is_start = unicode_ident::is_xid_start(first) || first == '_';
93 is_start && rest.chars().all(unicode_ident::is_xid_continue)
94 }
95
96 fn is_ident_continue(&self) -> bool {
97 let mut iter = self.as_str().chars();
98 iter.all(unicode_ident::is_xid_continue)
99 }
100}
101
102impl Sealed for char {}
103impl Char for char {
104 fn is_inline_whitespace(&self) -> bool {
105 *self == ' ' || *self == '\t'
106 }
107
108 fn is_whitespace(&self) -> bool {
109 unicode::is_whitespace(*self)
110 }
111
112 fn is_newline(&self) -> bool {
113 [
114 '\n', '\r', '\x0B', '\x0C', '\u{0085}', '\u{2028}', '\u{2029}', ]
122 .as_slice()
123 .contains(self)
124 }
125
126 fn digit_zero() -> Self {
127 '0'
128 }
129
130 fn is_digit(&self, radix: u32) -> bool {
131 char::is_digit(*self, radix)
132 }
133
134 fn to_ascii(&self) -> Option<u8> {
135 self.is_ascii().then_some(*self as u8)
136 }
137
138 fn is_ident_start(&self) -> bool {
139 unicode_ident::is_xid_start(*self) || *self == '_'
140 }
141
142 fn is_ident_continue(&self) -> bool {
143 unicode_ident::is_xid_continue(*self)
144 }
145}
146
147impl Sealed for u8 {}
148impl Char for u8 {
149 fn is_inline_whitespace(&self) -> bool {
150 *self == b' ' || *self == b'\t'
151 }
152
153 fn is_whitespace(&self) -> bool {
154 self.is_ascii_whitespace()
155 }
156
157 fn is_newline(&self) -> bool {
158 [
159 b'\n', b'\r', b'\x0B', b'\x0C', ]
164 .as_slice()
165 .contains(self)
166 }
167
168 fn digit_zero() -> Self {
169 b'0'
170 }
171
172 fn is_digit(&self, radix: u32) -> bool {
173 (*self as char).is_digit(radix)
174 }
175
176 fn to_ascii(&self) -> Option<u8> {
177 Some(*self)
178 }
179
180 fn is_ident_start(&self) -> bool {
181 (*self as char).is_ident_start()
182 }
183
184 fn is_ident_continue(&self) -> bool {
185 (*self as char).is_ident_continue()
186 }
187}
188
189#[derive(Copy, Clone)]
191pub struct Padded<A> {
192 pub(crate) parser: A,
193}
194
195impl<'src, I, O, E, A> Parser<'src, I, O, E> for Padded<A>
196where
197 I: ValueInput<'src>,
198 E: ParserExtra<'src, I>,
199 I::Token: Char,
200 A: Parser<'src, I, O, E>,
201{
202 fn go<M: Mode>(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult<M, O> {
203 inp.skip_while(|c| c.is_whitespace());
204 let out = self.parser.go::<M>(inp)?;
205 inp.skip_while(|c| c.is_whitespace());
206 Ok(out)
207 }
208
209 go_extra!(O);
210}
211
212#[non_exhaustive]
214pub enum TextExpected<'src, I: StrInput<'src>>
215where
216 I::Token: Char,
217{
218 Whitespace,
220 InlineWhitespace,
222 Newline,
224 Digit(Range<u32>),
231 IdentifierPart,
233 Identifier(I::Slice),
235}
236
237pub fn whitespace<'src, I, E>() -> Repeated<impl Parser<'src, I, (), E> + Copy, (), I, E>
255where
256 I: StrInput<'src>,
257 I::Token: Char + 'src,
258 E: ParserExtra<'src, I>,
259 E::Error: LabelError<'src, I, TextExpected<'src, I>>,
260{
261 any()
262 .filter(|c: &I::Token| c.is_whitespace())
263 .map_err(|mut err: E::Error| {
264 err.label_with(TextExpected::Whitespace);
265 err
266 })
267 .ignored()
268 .repeated()
269}
270
271pub fn inline_whitespace<'src, I, E>() -> Repeated<impl Parser<'src, I, (), E> + Copy, (), I, E>
291where
292 I: StrInput<'src>,
293 I::Token: Char + 'src,
294 E: ParserExtra<'src, I>,
295 E::Error: LabelError<'src, I, TextExpected<'src, I>>,
296{
297 any()
298 .filter(|c: &I::Token| c.is_inline_whitespace())
299 .map_err(|mut err: E::Error| {
300 err.label_with(TextExpected::InlineWhitespace);
301 err
302 })
303 .ignored()
304 .repeated()
305}
306
307#[must_use]
338pub fn newline<'src, I, E>() -> impl Parser<'src, I, (), E> + Copy
339where
340 I: StrInput<'src>,
341 I::Token: Char + 'src,
342 E: ParserExtra<'src, I>,
343 &'src str: OrderedSeq<'src, I::Token>,
344 E::Error: LabelError<'src, I, TextExpected<'src, I>>,
345{
346 custom(|inp| {
347 let before = inp.cursor();
348
349 if inp
350 .peek()
351 .map_or(false, |c: I::Token| c.to_ascii() == Some(b'\r'))
352 {
353 inp.skip();
354 if inp
355 .peek()
356 .map_or(false, |c: I::Token| c.to_ascii() == Some(b'\n'))
357 {
358 inp.skip();
359 }
360 Ok(())
361 } else {
362 let c = inp.next();
363 if c.map_or(false, |c: I::Token| c.is_newline()) {
364 Ok(())
365 } else {
366 let span = inp.span_since(&before);
367 Err(LabelError::expected_found(
368 [TextExpected::Newline],
369 c.map(MaybeRef::Val),
370 span,
371 ))
372 }
373 }
374 })
375}
376
377#[must_use]
399pub fn digits<'src, I, E>(
400 radix: u32,
401) -> Repeated<impl Parser<'src, I, <I as Input<'src>>::Token, E> + Copy, I::Token, I, E>
402where
403 I: StrInput<'src>,
404 I::Token: Char + 'src,
405 E: ParserExtra<'src, I>,
406 E::Error: LabelError<'src, I, TextExpected<'src, I>>,
407{
408 any()
409 .filter(move |c: &I::Token| c.is_digit(radix))
410 .map_err(move |mut err: E::Error| {
411 err.label_with(TextExpected::Digit(0..radix));
412 err
413 })
414 .repeated()
415 .at_least(1)
416}
417
418#[must_use]
449pub fn int<'src, I, E>(radix: u32) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy
450where
451 I: StrInput<'src>,
452 I::Token: Char + 'src,
453 E: ParserExtra<'src, I>,
454 E::Error:
455 LabelError<'src, I, TextExpected<'src, I>> + LabelError<'src, I, MaybeRef<'src, I::Token>>,
456{
457 any()
458 .filter(move |c: &I::Token| c.is_digit(radix) && c != &I::Token::digit_zero())
459 .map_err(move |mut err: E::Error| {
460 err.label_with(TextExpected::Digit(1..radix));
461 err
462 })
463 .then(
464 any()
465 .filter(move |c: &I::Token| c.is_digit(radix))
466 .map_err(move |mut err: E::Error| {
467 err.label_with(TextExpected::Digit(0..radix));
468 err
469 })
470 .repeated(),
471 )
472 .ignored()
473 .or(just(I::Token::digit_zero()).ignored())
474 .to_slice()
475}
476
477pub mod ascii {
479 use super::*;
480
481 #[must_use]
489 pub fn ident<'src, I, E>() -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy
490 where
491 I: StrInput<'src>,
492 I::Token: Char + 'src,
493 E: ParserExtra<'src, I>,
494 E::Error: LabelError<'src, I, TextExpected<'src, I>>,
495 {
496 any()
497 .filter(|c: &I::Token| {
498 c.to_ascii()
499 .map_or(false, |i| i.is_ascii_alphabetic() || i == b'_')
500 })
501 .map_err(|mut err: E::Error| {
502 err.label_with(TextExpected::IdentifierPart);
503 err
504 })
505 .then(
506 any()
507 .filter(|c: &I::Token| {
508 c.to_ascii()
509 .map_or(false, |i| i.is_ascii_alphanumeric() || i == b'_')
510 })
511 .map_err(|mut err: E::Error| {
512 err.label_with(TextExpected::IdentifierPart);
513 err
514 })
515 .repeated(),
516 )
517 .to_slice()
518 }
519
520 #[track_caller]
540 pub fn keyword<'src, I, S, E>(
541 keyword: S,
542 ) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Clone + 'src
543 where
544 I: StrInput<'src>,
545 I::Slice: PartialEq,
546 I::Token: Char + fmt::Debug + 'src,
547 S: PartialEq<I::Slice> + Clone + 'src,
548 E: ParserExtra<'src, I> + 'src,
549 E::Error: LabelError<'src, I, TextExpected<'src, I>> + LabelError<'src, I, S>,
550 {
551 ident()
568 .try_map(move |s: I::Slice, span| {
569 if keyword == s {
570 Ok(())
571 } else {
572 Err(LabelError::expected_found([keyword.clone()], None, span))
573 }
574 })
575 .to_slice()
576 }
577}
578
579pub use unicode::*;
581
582pub mod unicode {
584 use super::*;
585
586 use core::str::{Bytes, Chars};
587 use unicode_segmentation::UnicodeSegmentation;
588
589 #[derive(PartialEq, Eq)]
591 #[repr(transparent)]
592 pub struct Grapheme {
593 inner: str,
594 }
595
596 impl Grapheme {
597 fn new(inner: &str) -> &Self {
598 unsafe { &*(inner as *const str as *const Self) }
600 }
601
602 pub fn digit_zero() -> &'static Self {
604 Self::new("0")
605 }
606
607 pub fn code_points(&self) -> Chars<'_> {
609 self.inner.chars()
610 }
611
612 pub fn bytes(&self) -> Bytes<'_> {
614 self.inner.bytes()
615 }
616
617 pub fn as_str(&self) -> &str {
619 &self.inner
620 }
621
622 pub fn as_bytes(&self) -> &[u8] {
624 self.inner.as_bytes()
625 }
626
627 pub fn split(&self) -> (char, &str) {
629 let mut iter = self.inner.chars();
630 let first = iter.next().unwrap();
632 (first, iter.as_str())
633 }
634 }
635
636 impl fmt::Debug for Grapheme {
637 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638 f.write_str("g'")?;
639 for i in self.as_str().chars() {
640 write!(f, "{}", i.escape_debug())?;
641 }
642 f.write_str("'")?;
643 Ok(())
644 }
645 }
646
647 impl fmt::Display for Grapheme {
648 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649 fmt::Display::fmt(&self.inner, f)
650 }
651 }
652
653 impl AsRef<str> for Grapheme {
654 fn as_ref(&self) -> &str {
655 self.as_str()
656 }
657 }
658
659 impl AsRef<[u8]> for Grapheme {
660 fn as_ref(&self) -> &[u8] {
661 self.as_bytes()
662 }
663 }
664
665 impl AsRef<Grapheme> for Grapheme {
666 fn as_ref(&self) -> &Grapheme {
667 self
668 }
669 }
670
671 impl Borrow<str> for Grapheme {
672 fn borrow(&self) -> &str {
673 self.as_str()
674 }
675 }
676
677 impl Borrow<[u8]> for Grapheme {
678 fn borrow(&self) -> &[u8] {
679 self.as_bytes()
680 }
681 }
682
683 impl<'src> From<&'src Grapheme> for Box<Grapheme> {
684 fn from(value: &'src Grapheme) -> Self {
685 let value: Box<str> = Box::from(value.as_str());
686 unsafe { Box::from_raw(Box::into_raw(value) as *mut Grapheme) }
688 }
689 }
690
691 impl From<Box<Grapheme>> for Box<str> {
692 fn from(value: Box<Grapheme>) -> Self {
693 unsafe { Box::from_raw(Box::into_raw(value) as *mut str) }
695 }
696 }
697
698 impl From<Box<Grapheme>> for Box<[u8]> {
699 fn from(value: Box<Grapheme>) -> Self {
700 Box::<str>::from(value).into()
701 }
702 }
703
704 #[derive(PartialEq, Eq)]
706 #[repr(transparent)]
707 pub struct Graphemes {
708 inner: str,
709 }
710
711 impl Graphemes {
712 pub fn new(inner: &str) -> &Self {
714 unsafe { &*(inner as *const str as *const Self) }
716 }
717
718 pub fn iter(&self) -> GraphemesIter<'_> {
720 self.into_iter()
721 }
722
723 pub fn code_points(&self) -> Chars<'_> {
725 self.inner.chars()
726 }
727
728 pub fn bytes(&self) -> Bytes<'_> {
730 self.inner.bytes()
731 }
732
733 pub fn as_str(&self) -> &str {
735 &self.inner
736 }
737
738 pub fn as_bytes(&self) -> &[u8] {
740 self.inner.as_bytes()
741 }
742 }
743
744 impl fmt::Debug for Graphemes {
745 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746 f.write_str("g")?;
747 fmt::Debug::fmt(&self.inner, f)
748 }
749 }
750
751 impl fmt::Display for Graphemes {
752 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753 fmt::Display::fmt(&self.inner, f)
754 }
755 }
756
757 impl AsRef<str> for Graphemes {
758 fn as_ref(&self) -> &str {
759 self.as_str()
760 }
761 }
762
763 impl AsRef<[u8]> for Graphemes {
764 fn as_ref(&self) -> &[u8] {
765 self.as_bytes()
766 }
767 }
768
769 impl AsRef<Graphemes> for Graphemes {
770 fn as_ref(&self) -> &Graphemes {
771 self
772 }
773 }
774
775 impl Borrow<str> for Graphemes {
776 fn borrow(&self) -> &str {
777 self.as_str()
778 }
779 }
780
781 impl Borrow<[u8]> for Graphemes {
782 fn borrow(&self) -> &[u8] {
783 self.as_bytes()
784 }
785 }
786
787 impl<'src> From<&'src str> for &'src Graphemes {
788 fn from(value: &'src str) -> Self {
789 Graphemes::new(value)
790 }
791 }
792
793 impl<'src> From<&'src Graphemes> for &'src str {
794 fn from(value: &'src Graphemes) -> Self {
795 value.as_str()
796 }
797 }
798
799 impl<'src> From<&'src Graphemes> for Box<Graphemes> {
800 fn from(value: &'src Graphemes) -> Self {
801 value.as_str().into()
802 }
803 }
804
805 impl<'src> From<&'src str> for Box<Graphemes> {
806 fn from(value: &'src str) -> Self {
807 Box::<str>::from(value).into()
808 }
809 }
810
811 impl From<Box<str>> for Box<Graphemes> {
812 fn from(value: Box<str>) -> Self {
813 unsafe { Box::from_raw(Box::into_raw(value) as *mut Graphemes) }
815 }
816 }
817
818 impl From<Box<Graphemes>> for Box<str> {
819 fn from(value: Box<Graphemes>) -> Self {
820 unsafe { Box::from_raw(Box::into_raw(value) as *mut str) }
822 }
823 }
824
825 impl From<Box<Graphemes>> for Box<[u8]> {
826 fn from(value: Box<Graphemes>) -> Self {
827 Box::<str>::from(value).into()
828 }
829 }
830
831 impl<'src> IntoIterator for &'src Graphemes {
832 type Item = &'src Grapheme;
833
834 type IntoIter = GraphemesIter<'src>;
835
836 fn into_iter(self) -> Self::IntoIter {
837 GraphemesIter::new(self)
838 }
839 }
840
841 impl Sealed for &'_ Graphemes {}
842 impl<'src> StrInput<'src> for &'src Graphemes {
843 #[doc(hidden)]
844 fn stringify(slice: Self::Slice) -> String {
845 slice.to_string()
846 }
847 }
848
849 impl<'src> Input<'src> for &'src Graphemes {
850 type Cursor = usize;
851 type Span = SimpleSpan<usize>;
852
853 type Token = &'src Grapheme;
854 type MaybeToken = &'src Grapheme;
855
856 type Cache = Self;
857
858 #[inline]
859 fn begin(self) -> (Self::Cursor, Self::Cache) {
860 (0, self)
861 }
862
863 #[inline]
864 fn cursor_location(cursor: &Self::Cursor) -> usize {
865 *cursor
866 }
867
868 #[inline(always)]
869 unsafe fn next_maybe(
870 this: &mut Self::Cache,
871 cursor: &mut Self::Cursor,
872 ) -> Option<Self::MaybeToken> {
873 if *cursor < this.as_str().len() {
874 let c = this
882 .as_str()
883 .get_unchecked(*cursor..)
884 .graphemes(true)
885 .next()
886 .unwrap_unchecked();
887 *cursor += c.len();
888 Some(Grapheme::new(c))
889 } else {
890 None
891 }
892 }
893
894 #[inline(always)]
895 unsafe fn span(_this: &mut Self::Cache, range: Range<&Self::Cursor>) -> Self::Span {
896 (*range.start..*range.end).into()
897 }
898 }
899
900 impl<'src> ExactSizeInput<'src> for &'src Graphemes {
901 #[inline(always)]
902 unsafe fn span_from(this: &mut Self::Cache, range: RangeFrom<&Self::Cursor>) -> Self::Span {
903 (*range.start..this.as_str().len()).into()
904 }
905 }
906
907 impl<'src> ValueInput<'src> for &'src Graphemes {
908 #[inline(always)]
909 unsafe fn next(this: &mut Self::Cache, cursor: &mut Self::Cursor) -> Option<Self::Token> {
910 Self::next_maybe(this, cursor)
911 }
912 }
913
914 impl<'src> SliceInput<'src> for &'src Graphemes {
915 type Slice = Self;
916
917 #[inline(always)]
918 fn full_slice(this: &mut Self::Cache) -> Self::Slice {
919 *this
920 }
921
922 #[inline(always)]
923 unsafe fn slice(this: &mut Self::Cache, range: Range<&Self::Cursor>) -> Self::Slice {
924 Graphemes::new(&this.as_str()[*range.start..*range.end])
925 }
926
927 #[inline(always)]
928 unsafe fn slice_from(
929 this: &mut Self::Cache,
930 from: RangeFrom<&Self::Cursor>,
931 ) -> Self::Slice {
932 Graphemes::new(&this.as_str()[*from.start..])
933 }
934 }
935
936 #[derive(Debug, Clone)]
938 pub struct GraphemesIter<'src> {
939 iter: unicode_segmentation::Graphemes<'src>,
940 }
941
942 impl<'src> GraphemesIter<'src> {
943 pub fn new(graphemes: &'src Graphemes) -> Self {
945 Self {
946 iter: graphemes.as_str().graphemes(true),
947 }
948 }
949
950 pub fn as_str(self) -> &'src str {
952 self.iter.as_str()
953 }
954 }
955
956 impl<'src> Iterator for GraphemesIter<'src> {
957 type Item = &'src Grapheme;
958
959 #[inline]
960 fn size_hint(&self) -> (usize, Option<usize>) {
961 self.iter.size_hint()
962 }
963
964 #[inline]
965 fn next(&mut self) -> Option<Self::Item> {
966 self.iter.next().map(Grapheme::new)
967 }
968 }
969
970 impl DoubleEndedIterator for GraphemesIter<'_> {
971 #[inline]
972 fn next_back(&mut self) -> Option<Self::Item> {
973 self.iter.next_back().map(Grapheme::new)
974 }
975 }
976
977 #[must_use]
984 pub fn ident<'src, I, E>() -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Copy
985 where
986 I: StrInput<'src>,
987 I::Token: Char + 'src,
988 E: ParserExtra<'src, I>,
989 E::Error: LabelError<'src, I, TextExpected<'src, I>>,
990 {
991 any()
992 .filter(|c: &I::Token| c.is_ident_start())
993 .map_err(|mut err: E::Error| {
994 err.label_with(TextExpected::IdentifierPart);
995 err
996 })
997 .then(
998 any()
999 .filter(|c: &I::Token| c.is_ident_continue())
1000 .map_err(|mut err: E::Error| {
1001 err.label_with(TextExpected::IdentifierPart);
1002 err
1003 })
1004 .repeated(),
1005 )
1006 .to_slice()
1007 }
1008
1009 #[track_caller]
1029 pub fn keyword<'src, I, S, E>(
1030 keyword: S,
1031 ) -> impl Parser<'src, I, <I as SliceInput<'src>>::Slice, E> + Clone + 'src
1032 where
1033 I: StrInput<'src>,
1034 I::Slice: PartialEq,
1035 I::Token: Char + fmt::Debug + 'src,
1036 S: PartialEq<I::Slice> + Clone + 'src,
1037 E: ParserExtra<'src, I> + 'src,
1038 E::Error: LabelError<'src, I, TextExpected<'src, I>> + LabelError<'src, I, S>,
1039 {
1040 ident()
1061 .try_map(move |s: I::Slice, span| {
1062 if keyword.borrow() == &s {
1063 Ok(())
1064 } else {
1065 Err(LabelError::expected_found([keyword.clone()], None, span))
1066 }
1067 })
1068 .to_slice()
1069 }
1070
1071 pub fn is_whitespace(c: char) -> bool {
1074 c.is_whitespace()
1075 && !matches!(
1076 c,
1077 '\u{202A}'
1078 | '\u{202B}'
1079 | '\u{202C}'
1080 | '\u{202D}'
1081 | '\u{202E}'
1082 | '\u{2066}'
1083 | '\u{2067}'
1084 | '\u{2068}'
1085 | '\u{2069}'
1086 )
1087 }
1088}
1089
1090#[cfg(test)]
1091mod tests {
1092 use crate::prelude::*;
1093 use std::fmt;
1094
1095 fn make_ascii_kw_parser<'src, I>(s: I::Slice) -> impl Parser<'src, I, ()>
1096 where
1097 I: crate::StrInput<'src>,
1098 I::Slice: PartialEq + Clone,
1099 I::Token: crate::Char + fmt::Debug + 'src,
1100 {
1101 text::ascii::keyword(s).ignored()
1102 }
1103
1104 fn make_unicode_kw_parser<'src, I>(s: I::Slice) -> impl Parser<'src, I, ()>
1105 where
1106 I: crate::StrInput<'src>,
1107 I::Slice: PartialEq + Clone,
1108 I::Token: crate::Char + fmt::Debug + 'src,
1109 {
1110 text::unicode::keyword(s).ignored()
1111 }
1112
1113 fn test_ok<'src, P: Parser<'src, &'src str, &'src str>>(parser: P, input: &'src str) {
1114 assert_eq!(
1115 parser.parse(input),
1116 ParseResult {
1117 output: Some(input),
1118 errs: vec![]
1119 }
1120 );
1121 }
1122
1123 fn test_err<'src, P: Parser<'src, &'src str, &'src str>>(parser: P, input: &'src str) {
1124 assert_eq!(
1125 parser.parse(input),
1126 ParseResult {
1127 output: None,
1128 errs: vec![EmptyErr::default()]
1129 }
1130 );
1131 }
1132
1133 #[test]
1134 fn keyword_good() {
1135 make_ascii_kw_parser::<&str>("hello");
1136 make_ascii_kw_parser::<&str>("_42");
1137 make_ascii_kw_parser::<&str>("_42");
1138
1139 make_unicode_kw_parser::<&str>("שלום");
1140 make_unicode_kw_parser::<&str>("привет");
1141 make_unicode_kw_parser::<&str>("你好");
1142 }
1143
1144 #[test]
1145 fn ident() {
1146 let ident = text::ident::<&str, extra::Default>();
1147 test_ok(ident, "foo");
1148 test_ok(ident, "foo_bar");
1149 test_ok(ident, "foo_");
1150 test_ok(ident, "_foo");
1151 test_ok(ident, "_");
1152 test_ok(ident, "__");
1153 test_ok(ident, "__init__");
1154 test_err(ident, "");
1155 test_err(ident, ".");
1156 test_err(ident, "123");
1157 }
1158
1159 #[test]
1160 fn whitespace() {
1161 use crate::{whitespace, LabelError, TextExpected};
1162
1163 let parser = whitespace::<&str, extra::Err<Rich<_>>>().exactly(1);
1164
1165 assert_eq!(
1166 parser.parse("").into_output_errors(),
1167 (
1168 None,
1169 vec![LabelError::<&str, _>::expected_found(
1170 vec![TextExpected::<&str>::Whitespace],
1171 None,
1172 SimpleSpan::new((), 0..0)
1173 )]
1174 )
1175 );
1176 }
1177
1178 }