1use nom::{
2 AsChar, IResult, Input, Offset, Parser,
3 branch::alt,
4 character::streaming::char,
5 character::streaming::satisfy,
6 combinator::recognize,
7 error::ParseError,
8 multi::{fold_many0, many1_count},
9 sequence::{delimited, preceded},
10};
11
12use crate::{is_qdtext, is_quoted_pair, is_tchar};
13
14pub fn tchar<I, E>(input: I) -> IResult<I, char, E>
20where
21 I: Input,
22 <I as Input>::Item: AsChar,
23 E: ParseError<I>,
24{
25 satisfy(is_tchar).parse(input)
26}
27
28pub fn token<I, E>(input: I) -> IResult<I, I, E>
30where
31 I: Input + Copy + Offset,
32 <I as Input>::Item: AsChar,
33 E: ParseError<I>,
34{
35 recognize(many1_count(tchar)).parse(input)
36}
37
38pub fn qdtext<I, E>(input: I) -> IResult<I, char, E>
40where
41 I: Input + Copy,
42 <I as Input>::Item: AsChar,
43 E: ParseError<I>,
44{
45 satisfy(is_qdtext)(input)
46}
47
48fn quoted_pair<I, E>(input: I) -> IResult<I, char, E>
50where
51 I: Input + Copy,
52 <I as Input>::Item: AsChar,
53 E: ParseError<I>,
54{
55 preceded(char('\\'), satisfy(is_quoted_pair)).parse(input)
56}
57
58pub fn quoted_string<I, E>(input: I) -> IResult<I, String, E>
60where
61 I: Input + Copy + Offset,
62 <I as Input>::Item: AsChar,
63 E: ParseError<I>,
64{
65 delimited(
66 char('"'),
67 fold_many0(
68 alt((qdtext, quoted_pair)),
69 String::default,
70 |mut collection, input| {
71 collection.push(input);
72 collection
73 },
74 ),
75 char('"'),
76 )
77 .parse(input)
78}
79
80#[cfg(test)]
81mod tests {
82 use nom::{Err as OutCome, Needed};
83 use nom_language::error::VerboseError;
84
85 use crate::streaming::{quoted_string, tchar, token};
86
87 #[test]
88 fn test_tchar() {
89 assert_eq!(
90 tchar::<_, VerboseError<&str>>(""),
91 Err(OutCome::Incomplete(Needed::Unknown))
92 );
93 assert_eq!(tchar::<_, VerboseError<&str>>("mbbb"), Ok(("bbb", 'm')));
94 assert_eq!(tchar::<_, VerboseError<&str>>("!aa"), Ok(("aa", '!')));
95 assert!(matches!(
96 tchar::<_, VerboseError<&str>>(","),
97 Err(OutCome::Error(_))
98 ));
99 }
100
101 #[test]
102 fn test_token() {
103 assert!(matches!(
104 token::<_, VerboseError<&str>>(""),
105 Err(OutCome::Incomplete(Needed::Unknown))
106 ));
107 assert_eq!(
108 token::<_, VerboseError<&str>>("mbbb"),
109 Err(OutCome::Incomplete(Needed::Unknown))
110 );
111 assert_eq!(token::<_, VerboseError<&str>>("a,"), Ok((",", "a")));
112 assert!(matches!(
113 token::<_, VerboseError<&str>>(","),
114 Err(OutCome::Error(_))
115 ));
116 }
117
118 #[test]
119 fn test_quoted_string() {
120 assert_eq!(
121 quoted_string::<_, VerboseError<&str>>(r#""""#),
122 Ok(("", String::from(r#""#)))
123 );
124
125 assert_eq!(
126 quoted_string::<_, VerboseError<&str>>(r#""hello""#),
127 Ok(("", String::from(r#"hello"#)))
128 );
129
130 assert_eq!(
131 quoted_string::<_, VerboseError<&str>>(r#""\"hello""#),
132 Ok(("", String::from(r#""hello"#)))
133 );
134
135 assert!(matches!(
136 quoted_string::<_, VerboseError<&str>>(r#""awd"#),
137 Err(OutCome::Incomplete(Needed::Unknown))
138 ));
139
140 assert!(matches!(
141 quoted_string::<_, VerboseError<&str>>(r#" "text""#),
142 Err(OutCome::Error(_))
143 ));
144
145 assert_eq!(
146 quoted_string::<_, VerboseError<&str>>(r#""awd"trailing"#),
147 Ok(("trailing", String::from("awd")))
148 );
149 }
150}