1use nom::branch::alt;
2use nom::character::complete;
3use nom::combinator::{eof, map, not, opt, value};
4use nom::error::context;
5use nom::multi::{many0, many1};
6use nom::sequence::{preceded, tuple};
7
8use crate::ast::path::Path;
9use crate::parser::parsers::{Elms, UResult};
10use crate::parser::parsers::basic_parsers::*;
11
12#[inline]
13pub(crate) fn segment(i: Elms) -> UResult<Elms, String> {
14 map(many0(pchar), |sl| sl.into_iter().collect())(i)
15}
16
17#[inline]
18fn segment_without_colon(i: Elms) -> UResult<Elms, String> {
19 map(many0(pchar_without_colon), |sl| sl.into_iter().collect())(i)
20}
21
22#[inline]
23pub(crate) fn segment_nz(i: Elms) -> UResult<Elms, String> {
24 map(many1(pchar), |sl| sl.into_iter().collect())(i)
25}
26
27#[inline]
28pub(crate) fn segment_nz_nc(i: Elms) -> UResult<Elms, String> {
29 map(
30 many1(alt((
31 map(unreserved, |c| c.into()),
33 pct_encoded,
34 map(sub_delims, |c| c.into()),
36 map(complete::char('@'), |c| c.into()),
37 ))),
38 |sl| sl.into_iter().collect(),
39 )(i)
40}
41
42#[inline]
43pub(crate) fn path_abempty(i: Elms) -> UResult<Elms, Path> {
44 map(many0(preceded(complete::char('/'), segment)), |sl| {
45 Path::of_abempty_from_strings(&sl)
46 })(i)
47}
48
49#[inline]
50pub(crate) fn path_absolute(i: Elms) -> UResult<Elms, Path> {
51 context(
52 "path_absolute",
53 map(
54 preceded(
55 complete::char('/'),
56 opt(map(
57 tuple((segment_nz, many0(preceded(complete::char('/'), segment)))),
58 |(s, sl)| {
59 let mut r = vec![s];
60 r.extend(sl);
61 r
62 },
63 )),
64 ),
65 |sl_opt| Path::of_absolute_from_strings(&sl_opt.unwrap_or(Vec::new())),
66 ),
67 )(i)
68}
69
70pub(crate) fn path_no_scheme(i: Elms) -> UResult<Elms, Path> {
71 log::debug!("path_no_scheme = {}", i.as_str().unwrap());
72 let result = context(
73 "path_no_scheme",
74 map(
75 tuple((segment_nz_nc, many0(preceded(complete::char('/'), segment)))),
76 |(s, sl)| {
77 let mut parts = vec![s];
78 parts.extend(sl);
79 Path::of_no_scheme_from_strings(&parts)
80 },
81 ),
82 )(i);
83 match result {
84 Ok((p1, p2)) => {
85 log::debug!("p1 = {}", p1);
86 log::debug!("p2 = {}", p2);
87 Ok((p1, p2))
88 }
89 Err(e) => {
90 log::debug!("e = {}", e);
91 Err(e)
92 }
93 }
94}
95
96pub(crate) fn path_rootless(i: Elms) -> UResult<Elms, Path> {
97 context(
98 "path_rootless",
99 map(
100 tuple((segment_nz, many0(preceded(complete::char('/'), segment)))),
101 |(s, sl)| {
102 let mut parts = vec![s];
103 parts.extend(sl);
104 Path::of_rootless_from_strings(&parts)
105 },
106 ),
107 )(i)
108}
109
110#[inline]
111pub(crate) fn path_empty(i: Elms) -> UResult<Elms, Path> {
112 context("path_empty", value(Path::of_empty(), eof))(i)
113}
114
115#[inline]
116pub(crate) fn path_without_abempty(i: Elms) -> UResult<Elms, Path> {
117 let is_absolute = opt(preceded(complete::char('/'), not(complete::char('/'))))(i.clone())
118 .map(|(_, v)| v.is_some())?;
119 let is_no_scheme =
120 opt(segment_nz_nc)(i.clone()).map(|(_, v)| v.iter().any(|s| !s.contains(':')))?;
121 let is_empty = opt(eof)(i.clone()).map(|(_, v)| v.is_some())?;
122
123 log::debug!("is_absolute = {}", is_absolute);
124 log::debug!("is_no_scheme = {}", is_no_scheme);
125 log::debug!("is_empty = {}", is_empty);
126
127 if is_empty {
128 path_empty(i.clone())
129 } else {
130 if is_absolute {
131 path_absolute(i)
132 } else {
135 path_rootless(i)
136 }
137 }
138}
139
140#[cfg(test)]
141pub mod gens {
142 use std::fmt::Formatter;
143
144 use prop_check_rs::gen::{Gen, Gens};
145
146 use crate::parser::parsers::basic_parsers::gens::*;
147
148 pub fn segment_str_gen() -> Gen<String> {
149 pchar_str_gen(0, u8::MAX - 1)
150 }
151
152 pub fn segment_nz_str_gen() -> Gen<String> {
153 pchar_str_gen(1, u8::MAX - 1)
154 }
155
156 pub fn segment_nz_nc_str_gen() -> Gen<String> {
157 rep_str_gen(1, u8::MAX - 1, || {
158 Gens::choose_u8(1, 2).bind(|n| match n {
159 1 => unreserved_char_gen().fmap(|c| c.into()),
160 2 => pct_encoded_str_gen(),
161 3 => sub_delims_char_gen().fmap(|c| c.into()),
162 4 => Gens::one_of_vec(vec!['@']).fmap(|c| c.into()),
163 x => panic!("x = {}", x),
164 })
165 })
166 }
167
168 pub fn path_abempty_str_gen() -> Gen<String> {
169 rep_str_gen(1, 10, || segment_str_gen().fmap(|s| format!("/{}", s)))
170 }
171
172 pub fn path_absolute_str_gen() -> Gen<String> {
173 rep_str_gen(1, 10, || segment_nz_str_gen().fmap(|s| format!("/{}", s))).bind(|s1| {
174 path_abempty_str_gen().fmap(move |s2| {
175 let prefix = if !s1.starts_with("/") { "/" } else { "" };
176 format!("{}{}{}", prefix, s1, s2)
177 })
178 })
179 }
180
181 pub fn path_no_scheme_str_gen() -> Gen<String> {
182 segment_nz_nc_str_gen().bind(|s1| {
183 rep_str_gen(1, 10, || segment_str_gen().fmap(|s2| format!("/{}", s2)))
184 .fmap(move |s2| format!("{}{}", s1, s2))
185 })
186 }
187
188 pub fn path_rootless_str_gen() -> Gen<String> {
189 segment_nz_str_gen().bind(|s1| {
190 rep_str_gen(1, 10, || segment_str_gen().fmap(|s2| format!("/{}", s2)))
191 .fmap(move |s2| format!("{}{}", s1, s2))
192 })
193 }
194
195 #[derive(Clone, Debug)]
196 pub struct Pair<A, B>(pub(crate) A, pub(crate) B);
197
198 impl<A, B> std::fmt::Display for Pair<A, B>
199 where
200 A: std::fmt::Display,
201 B: std::fmt::Display,
202 {
203 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204 write!(f, "({},{})", self.0, self.1)
205 }
206 }
207
208 pub fn path_str_with_abempty_gen() -> Gen<Pair<String, String>> {
209 Gens::choose_u8(1, 5).bind(|n| match n {
210 1 => path_abempty_str_gen().fmap(|s| Pair("abempty_path".to_string(), s)),
211 2 => path_absolute_str_gen().fmap(|s| Pair("absolute_path".to_string(), s)),
212 3 => path_no_scheme_str_gen().fmap(|s| Pair("no_scheme_path".to_string(), s)),
213 4 => path_rootless_str_gen().fmap(|s| Pair("rootless_path".to_string(), s)),
214 5 => Gen::<String>::unit(|| Pair("empty_path".to_string(), "".to_string())),
215 x => panic!("x = {}", x),
216 })
217 }
218
219 pub fn path_str_without_abempty_gen() -> Gen<Pair<String, String>> {
220 Gens::choose_u8(1, 3).bind(|n| match n {
221 1 => path_absolute_str_gen().fmap(|s| Pair("absolute_path".to_string(), s)),
222 2 => path_rootless_str_gen().fmap(|s| Pair("rootless_path".to_string(), s)),
223 3 => Gen::<String>::unit(|| Pair("empty_path".to_string(), "".to_string())),
224 x => panic!("x = {}", x),
225 })
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use std::env;
232
233 use anyhow::Result;
234
235 use prop_check_rs::prop;
236 use prop_check_rs::prop::TestCases;
237 use prop_check_rs::rng::RNG;
238
239 use super::*;
240 use super::gens::*;
241
242 const TEST_COUNT: TestCases = 100;
243
244 fn init() {
245 env::set_var("RUST_LOG", "debug");
246 let _ = env_logger::builder().is_test(true).try_init();
247 }
248
249 #[test]
250 fn test_segment() -> Result<()> {
251 init();
252 let mut counter = 0;
253 let prop = prop::for_all(
254 || segment_str_gen(),
255 move |s| {
256 counter += 1;
257 log::debug!("{}, segment = {}", counter, s);
258 let (_, r) = segment(Elms::new(s.as_bytes())).ok().unwrap();
259 assert_eq!(r, s);
260 true
261 },
262 );
263 prop::test_with_prop(prop, 5, 1000, RNG::new())
264 }
265
266 #[test]
267 fn test_segment_nz() -> Result<()> {
268 init();
269 let mut counter = 0;
270 let prop = prop::for_all(
271 || segment_nz_str_gen(),
272 move |s| {
273 counter += 1;
274 log::debug!("{}, value = {}", counter, s);
275 let (_, r) = segment_nz(Elms::new(s.as_bytes())).ok().unwrap();
276 assert_eq!(r, s);
277 true
278 },
279 );
280 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
281 }
282
283 #[test]
284 fn test_segment_nz_nc() -> Result<()> {
285 init();
286 let mut counter = 0;
287 let prop = prop::for_all(
288 || segment_nz_nc_str_gen(),
289 move |s| {
290 counter += 1;
291 log::debug!("{}, segment_nz_nc = {}", counter, s);
292 let (_, r) = segment_nz_nc(Elms::new(s.as_bytes())).ok().unwrap();
293 assert_eq!(r, s);
294 true
295 },
296 );
297 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
298 }
299
300 #[test]
301 fn test_path_abempty() -> Result<()> {
302 init();
303 let mut counter = 0;
304 let prop = prop::for_all(
305 || path_abempty_str_gen(),
306 move |s| {
307 counter += 1;
308 log::debug!("{:>03}, path_abempty = {}", counter, s);
309 let (_, r) = path_abempty(Elms::new(s.as_bytes())).ok().unwrap();
310 assert_eq!(r.to_string(), s);
311 true
312 },
313 );
314 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
315 }
316
317 #[test]
318 fn test_path_absolute() -> Result<()> {
319 init();
320 let mut counter = 0;
321 let prop = prop::for_all(
322 || path_absolute_str_gen(),
323 move |s| {
324 counter += 1;
325 log::debug!("{:>03}, path_absolute = {}", counter, s);
326 let (_, r) = path_absolute(Elms::new(s.as_bytes())).ok().unwrap();
327 assert_eq!(r.to_string(), s);
328 true
329 },
330 );
331 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
332 }
333
334 #[test]
335 fn test_path_no_scheme() -> Result<()> {
336 init();
337 let mut counter = 0;
338 let prop = prop::for_all(
339 || path_no_scheme_str_gen(),
340 move |s| {
341 counter += 1;
342 log::debug!("{:>03}, path_no_scheme = {}", counter, s);
343 let (_, r) = path_no_scheme(Elms::new(s.as_bytes())).ok().unwrap();
344 assert_eq!(r.to_string(), s);
345 true
346 },
347 );
348 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
349 }
350
351 #[test]
352 fn test_path_rootless() -> Result<()> {
353 init();
354 let mut counter = 0;
355 let prop = prop::for_all(
356 || path_rootless_str_gen(),
357 move |s| {
358 counter += 1;
359 log::debug!("{:>03}, path_rootless = {}", counter, s);
360 let (_, r) = path_rootless(Elms::new(s.as_bytes())).ok().unwrap();
361 assert_eq!(r.to_string(), s);
362 true
363 },
364 );
365 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
366 }
367
368 #[test]
369 fn test_path_without_abempty() -> Result<()> {
370 init();
371 let mut counter = 0;
372 let prop = prop::for_all(
373 || path_str_without_abempty_gen(),
374 move |s| {
375 counter += 1;
376 log::debug!(
377 "{:>03}, path_type = {:?}, path_without_abempty = {:?}",
378 counter,
379 s.0,
380 s.1
381 );
382 let (_, r) = path_without_abempty(Elms::new(s.1.as_bytes()))
383 .ok()
384 .unwrap();
385 assert_eq!(r.type_name(), s.0);
386 assert_eq!(r.to_string(), s.1);
387 true
388 },
389 );
390 prop::test_with_prop(prop, 5, TEST_COUNT, RNG::new())
391 }
392}