1use std::str::FromStr;
2
3use winnow::ascii::digit1;
4use winnow::ascii::space0;
5use winnow::combinator::cut_err;
6use winnow::combinator::dispatch;
7use winnow::combinator::fail;
8use winnow::combinator::opt;
9use winnow::combinator::repeat;
10use winnow::combinator::seq;
11use winnow::error::ContextError;
12use winnow::error::ParseError;
13use winnow::error::StrContext;
14use winnow::error::StrContextValue;
15use winnow::prelude::*;
16use winnow::token::any;
17use winnow::token::take_while;
18
19use crate::path::Expression;
20use crate::path::Postfix;
21
22pub(crate) fn from_str(input: &str) -> Result<Expression, ParseError<&str, ContextError>> {
23 path.parse(input)
24}
25
26fn path(i: &mut &str) -> ModalResult<Expression> {
27 let root = ident.parse_next(i)?;
28 let postfix = repeat(0.., postfix).parse_next(i)?;
29 let expr = Expression { root, postfix };
30 Ok(expr)
31}
32
33fn postfix(i: &mut &str) -> ModalResult<Postfix> {
34 dispatch! {any;
35 '[' => cut_err(
36 seq!(
37 integer.map(Postfix::Index),
38 _: ']'.context(StrContext::Expected(StrContextValue::CharLiteral(']'))),
39 )
40 .map(|(i,)| i)
41 .context(StrContext::Label("subscript"))
42 ),
43 '.' => cut_err(ident.map(Postfix::Key)),
44 _ => cut_err(
45 fail
46 .context(StrContext::Label("postfix"))
47 .context(StrContext::Expected(StrContextValue::CharLiteral('[')))
48 .context(StrContext::Expected(StrContextValue::CharLiteral('.')))
49 ),
50 }
51 .parse_next(i)
52}
53
54fn ident(i: &mut &str) -> ModalResult<String> {
55 take_while(1.., ('a'..='z', 'A'..='Z', '0'..='9', '_', '-'))
56 .map(ToOwned::to_owned)
57 .context(StrContext::Label("identifier"))
58 .context(StrContext::Expected(StrContextValue::Description(
59 "ASCII alphanumeric",
60 )))
61 .context(StrContext::Expected(StrContextValue::CharLiteral('_')))
62 .context(StrContext::Expected(StrContextValue::CharLiteral('-')))
63 .parse_next(i)
64}
65
66fn integer(i: &mut &str) -> ModalResult<isize> {
67 seq!(
68 _: space0,
69 (opt('-'), digit1).take().try_map(FromStr::from_str),
70 _: space0
71 )
72 .context(StrContext::Expected(StrContextValue::Description(
73 "integer",
74 )))
75 .map(|(i,)| i)
76 .parse_next(i)
77}
78
79#[cfg(test)]
80mod test {
81 use snapbox::prelude::*;
82 use snapbox::{assert_data_eq, str};
83
84 use super::*;
85
86 #[test]
87 fn test_id() {
88 let parsed: Expression = from_str("abcd").unwrap();
89 assert_data_eq!(
90 parsed.to_debug(),
91 str![[r#"
92Expression {
93 root: "abcd",
94 postfix: [],
95}
96
97"#]]
98 );
99 }
100
101 #[test]
102 fn test_id_dash() {
103 let parsed: Expression = from_str("abcd-efgh").unwrap();
104 assert_data_eq!(
105 parsed.to_debug(),
106 str![[r#"
107Expression {
108 root: "abcd-efgh",
109 postfix: [],
110}
111
112"#]]
113 );
114 }
115
116 #[test]
117 fn test_child() {
118 let parsed: Expression = from_str("abcd.efgh").unwrap();
119 assert_data_eq!(
120 parsed.to_debug(),
121 str![[r#"
122Expression {
123 root: "abcd",
124 postfix: [
125 Key(
126 "efgh",
127 ),
128 ],
129}
130
131"#]]
132 );
133
134 let parsed: Expression = from_str("abcd.efgh.ijkl").unwrap();
135 assert_data_eq!(
136 parsed.to_debug(),
137 str![[r#"
138Expression {
139 root: "abcd",
140 postfix: [
141 Key(
142 "efgh",
143 ),
144 Key(
145 "ijkl",
146 ),
147 ],
148}
149
150"#]]
151 );
152 }
153
154 #[test]
155 fn test_subscript() {
156 let parsed: Expression = from_str("abcd[12]").unwrap();
157 assert_data_eq!(
158 parsed.to_debug(),
159 str![[r#"
160Expression {
161 root: "abcd",
162 postfix: [
163 Index(
164 12,
165 ),
166 ],
167}
168
169"#]]
170 );
171 }
172
173 #[test]
174 fn test_subscript_neg() {
175 let parsed: Expression = from_str("abcd[-1]").unwrap();
176 assert_data_eq!(
177 parsed.to_debug(),
178 str![[r#"
179Expression {
180 root: "abcd",
181 postfix: [
182 Index(
183 -1,
184 ),
185 ],
186}
187
188"#]]
189 );
190 }
191
192 #[test]
193 fn test_invalid_identifier() {
194 let err = from_str("!").unwrap_err();
195 assert_data_eq!(
196 err.to_string(),
197 str![[r#"
198!
199^
200invalid identifier
201expected ASCII alphanumeric, `_`, `-`
202"#]]
203 );
204 }
205
206 #[test]
207 fn test_invalid_child() {
208 let err = from_str("a..").unwrap_err();
209 assert_data_eq!(
210 err.to_string(),
211 str![[r#"
212a..
213 ^
214invalid identifier
215expected ASCII alphanumeric, `_`, `-`
216"#]]
217 );
218 }
219
220 #[test]
221 fn test_invalid_subscript() {
222 let err = from_str("a[b]").unwrap_err();
223 assert_data_eq!(
224 err.to_string(),
225 str![[r#"
226a[b]
227 ^
228invalid subscript
229expected integer
230"#]]
231 );
232 }
233
234 #[test]
235 fn test_incomplete_subscript() {
236 let err = from_str("a[0").unwrap_err();
237 assert_data_eq!(
238 err.to_string(),
239 str![[r#"
240a[0
241 ^
242invalid subscript
243expected `]`
244"#]]
245 );
246 }
247
248 #[test]
249 fn test_invalid_postfix() {
250 let err = from_str("a!b").unwrap_err();
251 assert_data_eq!(
252 err.to_string(),
253 str![[r#"
254a!b
255 ^
256invalid postfix
257expected `[`, `.`
258"#]]
259 );
260 }
261}