/rust/registry/src/index.crates.io-6f17d22bba15001f/pest-2.8.1/src/position.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // pest. The Elegant Parser |
2 | | // Copyright (c) 2018 Dragoș Tiselice |
3 | | // |
4 | | // Licensed under the Apache License, Version 2.0 |
5 | | // <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT |
6 | | // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
7 | | // option. All files in the project carrying such notice may not be copied, |
8 | | // modified, or distributed except according to those terms. |
9 | | |
10 | | use core::cmp::Ordering; |
11 | | use core::fmt; |
12 | | use core::hash::{Hash, Hasher}; |
13 | | use core::ops::Range; |
14 | | use core::ptr; |
15 | | use core::str; |
16 | | |
17 | | use crate::span; |
18 | | |
19 | | /// A cursor position in a `&str` which provides useful methods to manually parse that string. |
20 | | #[derive(Clone, Copy)] |
21 | | pub struct Position<'i> { |
22 | | input: &'i str, |
23 | | pos: usize, |
24 | | } |
25 | | |
26 | | impl<'i> Position<'i> { |
27 | | /// Create a new `Position` without checking invariants. (Checked with `debug_assertions`.) |
28 | 0 | pub(crate) fn new_internal(input: &str, pos: usize) -> Position<'_> { |
29 | 0 | debug_assert!(input.get(pos..).is_some()); |
30 | 0 | Position { input, pos } |
31 | 0 | } |
32 | | |
33 | | /// Attempts to create a new `Position` at the given position. If the specified position is |
34 | | /// an invalid index, or the specified position is not a valid UTF8 boundary, then None is |
35 | | /// returned. |
36 | | /// |
37 | | /// # Examples |
38 | | /// ``` |
39 | | /// # use pest::Position; |
40 | | /// let cheart = '💖'; |
41 | | /// let heart = "💖"; |
42 | | /// assert_eq!(Position::new(heart, 1), None); |
43 | | /// assert_ne!(Position::new(heart, cheart.len_utf8()), None); |
44 | | /// ``` |
45 | 0 | pub fn new(input: &str, pos: usize) -> Option<Position<'_>> { |
46 | 0 | input.get(pos..).map(|_| Position { input, pos }) |
47 | 0 | } |
48 | | |
49 | | /// Creates a `Position` at the start of a `&str`. |
50 | | /// |
51 | | /// # Examples |
52 | | /// |
53 | | /// ``` |
54 | | /// # use pest::Position; |
55 | | /// let start = Position::from_start(""); |
56 | | /// assert_eq!(start.pos(), 0); |
57 | | /// ``` |
58 | | #[inline] |
59 | 0 | pub fn from_start(input: &'i str) -> Position<'i> { |
60 | 0 | // Position 0 is always safe because it's always a valid UTF-8 border. |
61 | 0 | Position { input, pos: 0 } |
62 | 0 | } Unexecuted instantiation: <pest::position::Position>::from_start Unexecuted instantiation: <pest::position::Position>::from_start |
63 | | |
64 | | /// Returns the byte position of this `Position` as a `usize`. |
65 | | /// |
66 | | /// # Examples |
67 | | /// |
68 | | /// ``` |
69 | | /// # use pest::Position; |
70 | | /// let input = "ab"; |
71 | | /// let mut start = Position::from_start(input); |
72 | | /// |
73 | | /// assert_eq!(start.pos(), 0); |
74 | | /// ``` |
75 | | #[inline] |
76 | 0 | pub fn pos(&self) -> usize { |
77 | 0 | self.pos |
78 | 0 | } Unexecuted instantiation: <pest::position::Position>::pos Unexecuted instantiation: <pest::position::Position>::pos |
79 | | |
80 | | /// Creates a `Span` from two `Position`s. |
81 | | /// |
82 | | /// # Panics |
83 | | /// |
84 | | /// Panics if the positions come from different inputs. |
85 | | /// |
86 | | /// # Examples |
87 | | /// |
88 | | /// ``` |
89 | | /// # use pest::Position; |
90 | | /// let input = "ab"; |
91 | | /// let start = Position::from_start(input); |
92 | | /// let span = start.span(&start.clone()); |
93 | | /// |
94 | | /// assert_eq!(span.start(), 0); |
95 | | /// assert_eq!(span.end(), 0); |
96 | | /// ``` |
97 | | #[inline] |
98 | 0 | pub fn span(&self, other: &Position<'i>) -> span::Span<'i> { |
99 | 0 | if ptr::eq(self.input, other.input) |
100 | | /* && self.input.get(self.pos..other.pos).is_some() */ |
101 | | { |
102 | 0 | span::Span::new_internal(self.input, self.pos, other.pos) |
103 | | } else { |
104 | | // TODO: maybe a panic if self.pos < other.pos |
105 | 0 | panic!("span created from positions from different inputs") |
106 | | } |
107 | 0 | } |
108 | | |
109 | | /// Returns the line and column number of this `Position`. |
110 | | /// |
111 | | /// This is an O(n) operation, where n is the number of chars in the input. |
112 | | /// You better use [`pair.line_col()`](struct.Pair.html#method.line_col) instead. |
113 | | /// |
114 | | /// # Examples |
115 | | /// |
116 | | /// ``` |
117 | | /// # use pest; |
118 | | /// # #[allow(non_camel_case_types)] |
119 | | /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
120 | | /// enum Rule {} |
121 | | /// |
122 | | /// let input = "\na"; |
123 | | /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input); |
124 | | /// let mut result = state.match_string("\na"); |
125 | | /// assert!(result.is_ok()); |
126 | | /// assert_eq!(result.unwrap().position().line_col(), (2, 2)); |
127 | | /// ``` |
128 | | #[inline] |
129 | 0 | pub fn line_col(&self) -> (usize, usize) { |
130 | 0 | if self.pos > self.input.len() { |
131 | 0 | panic!("position out of bounds"); |
132 | 0 | } |
133 | 0 | let mut pos = self.pos; |
134 | 0 | let slice = &self.input[..pos]; |
135 | 0 | let mut chars = slice.chars().peekable(); |
136 | 0 |
|
137 | 0 | let mut line_col = (1, 1); |
138 | | |
139 | 0 | while pos != 0 { |
140 | 0 | match chars.next() { |
141 | | Some('\r') => { |
142 | 0 | if let Some(&'\n') = chars.peek() { |
143 | 0 | chars.next(); |
144 | 0 |
|
145 | 0 | if pos == 1 { |
146 | 0 | pos -= 1; |
147 | 0 | } else { |
148 | 0 | pos -= 2; |
149 | 0 | } |
150 | | |
151 | 0 | line_col = (line_col.0 + 1, 1); |
152 | 0 | } else { |
153 | 0 | pos -= 1; |
154 | 0 | line_col = (line_col.0, line_col.1 + 1); |
155 | 0 | } |
156 | | } |
157 | 0 | Some('\n') => { |
158 | 0 | pos -= 1; |
159 | 0 | line_col = (line_col.0 + 1, 1); |
160 | 0 | } |
161 | 0 | Some(c) => { |
162 | 0 | pos -= c.len_utf8(); |
163 | 0 | line_col = (line_col.0, line_col.1 + 1); |
164 | 0 | } |
165 | 0 | None => unreachable!(), |
166 | | } |
167 | | } |
168 | | |
169 | 0 | line_col |
170 | 0 | } Unexecuted instantiation: <pest::position::Position>::line_col Unexecuted instantiation: <pest::position::Position>::line_col |
171 | | |
172 | | /// Returns the entire line of the input that contains this `Position`. |
173 | | /// |
174 | | /// # Examples |
175 | | /// |
176 | | /// ``` |
177 | | /// # use pest; |
178 | | /// # #[allow(non_camel_case_types)] |
179 | | /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
180 | | /// enum Rule {} |
181 | | /// |
182 | | /// let input = "\na"; |
183 | | /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input); |
184 | | /// let mut result = state.match_string("\na"); |
185 | | /// assert!(result.is_ok()); |
186 | | /// assert_eq!(result.unwrap().position().line_of(), "a"); |
187 | | /// ``` |
188 | | #[inline] |
189 | 0 | pub fn line_of(&self) -> &'i str { |
190 | 0 | if self.pos > self.input.len() { |
191 | 0 | panic!("position out of bounds"); |
192 | 0 | }; |
193 | 0 | // Safe since start and end can only be valid UTF-8 borders. |
194 | 0 | &self.input[self.find_line_start()..self.find_line_end()] |
195 | 0 | } Unexecuted instantiation: <pest::position::Position>::line_of Unexecuted instantiation: <pest::position::Position>::line_of |
196 | | |
197 | 0 | pub(crate) fn find_line_start(&self) -> usize { |
198 | 0 | if self.input.is_empty() { |
199 | 0 | return 0; |
200 | 0 | }; |
201 | 0 | // Position's pos is always a UTF-8 border. |
202 | 0 | let start = self |
203 | 0 | .input |
204 | 0 | .char_indices() |
205 | 0 | .rev() |
206 | 0 | .skip_while(|&(i, _)| i >= self.pos) |
207 | 0 | .find(|&(_, c)| c == '\n'); |
208 | 0 | match start { |
209 | 0 | Some((i, _)) => i + 1, |
210 | 0 | None => 0, |
211 | | } |
212 | 0 | } |
213 | | |
214 | 0 | pub(crate) fn find_line_end(&self) -> usize { |
215 | 0 | if self.input.is_empty() { |
216 | 0 | 0 |
217 | 0 | } else if self.pos == self.input.len() - 1 { |
218 | 0 | self.input.len() |
219 | | } else { |
220 | | // Position's pos is always a UTF-8 border. |
221 | 0 | let end = self |
222 | 0 | .input |
223 | 0 | .char_indices() |
224 | 0 | .skip_while(|&(i, _)| i < self.pos) |
225 | 0 | .find(|&(_, c)| c == '\n'); |
226 | 0 | match end { |
227 | 0 | Some((i, _)) => i + 1, |
228 | 0 | None => self.input.len(), |
229 | | } |
230 | | } |
231 | 0 | } |
232 | | |
233 | | /// Returns `true` when the `Position` points to the start of the input `&str`. |
234 | | #[inline] |
235 | 0 | pub(crate) fn at_start(&self) -> bool { |
236 | 0 | self.pos == 0 |
237 | 0 | } Unexecuted instantiation: <pest::position::Position>::at_start Unexecuted instantiation: <pest::position::Position>::at_start |
238 | | |
239 | | /// Returns `true` when the `Position` points to the end of the input `&str`. |
240 | | #[inline] |
241 | 0 | pub(crate) fn at_end(&self) -> bool { |
242 | 0 | self.pos == self.input.len() |
243 | 0 | } Unexecuted instantiation: <pest::position::Position>::at_end Unexecuted instantiation: <pest::position::Position>::at_end |
244 | | |
245 | | /// Skips `n` `char`s from the `Position` and returns `true` if the skip was possible or `false` |
246 | | /// otherwise. If the return value is `false`, `pos` will not be updated. |
247 | | #[inline] |
248 | 0 | pub(crate) fn skip(&mut self, n: usize) -> bool { |
249 | 0 | let skipped = { |
250 | 0 | let mut len = 0; |
251 | 0 | // Position's pos is always a UTF-8 border. |
252 | 0 | let mut chars = self.input[self.pos..].chars(); |
253 | 0 | for _ in 0..n { |
254 | 0 | if let Some(c) = chars.next() { |
255 | 0 | len += c.len_utf8(); |
256 | 0 | } else { |
257 | 0 | return false; |
258 | | } |
259 | | } |
260 | 0 | len |
261 | 0 | }; |
262 | 0 |
|
263 | 0 | self.pos += skipped; |
264 | 0 | true |
265 | 0 | } |
266 | | |
267 | | /// Goes back `n` `char`s from the `Position` and returns `true` if the skip was possible or `false` |
268 | | /// otherwise. If the return value is `false`, `pos` will not be updated. |
269 | | #[inline] |
270 | 0 | pub(crate) fn skip_back(&mut self, n: usize) -> bool { |
271 | 0 | let skipped = { |
272 | 0 | let mut len = 0; |
273 | 0 | // Position's pos is always a UTF-8 border. |
274 | 0 | let mut chars = self.input[..self.pos].chars().rev(); |
275 | 0 | for _ in 0..n { |
276 | 0 | if let Some(c) = chars.next() { |
277 | 0 | len += c.len_utf8(); |
278 | 0 | } else { |
279 | 0 | return false; |
280 | | } |
281 | | } |
282 | 0 | len |
283 | 0 | }; |
284 | 0 |
|
285 | 0 | self.pos -= skipped; |
286 | 0 | true |
287 | 0 | } |
288 | | |
289 | | /// Skips until one of the given `strings` is found. If none of the `strings` can be found, |
290 | | /// this function will return `false` but its `pos` will *still* be updated. |
291 | | #[inline] |
292 | 0 | pub(crate) fn skip_until(&mut self, strings: &[&str]) -> bool { |
293 | | #[cfg(not(feature = "memchr"))] |
294 | | { |
295 | | self.skip_until_basic(strings) |
296 | | } |
297 | | #[cfg(feature = "memchr")] |
298 | | { |
299 | 0 | match strings { |
300 | 0 | [] => (), |
301 | 0 | [s1] => { |
302 | 0 | if let Some(from) = |
303 | 0 | memchr::memmem::find(&self.input.as_bytes()[self.pos..], s1.as_bytes()) |
304 | | { |
305 | 0 | self.pos += from; |
306 | 0 | return true; |
307 | 0 | } |
308 | | } |
309 | 0 | [s1, s2] if !s1.is_empty() && !s2.is_empty() => { |
310 | 0 | let b1 = s1.as_bytes()[0]; |
311 | 0 | let b2 = s2.as_bytes()[0]; |
312 | 0 | let miter = memchr::memchr2_iter(b1, b2, &self.input.as_bytes()[self.pos..]); |
313 | 0 | for from in miter { |
314 | 0 | let start = &self.input[self.pos + from..]; |
315 | 0 | if start.starts_with(s1) || start.starts_with(s2) { |
316 | 0 | self.pos += from; |
317 | 0 | return true; |
318 | 0 | } |
319 | | } |
320 | | } |
321 | 0 | [s1, s2, s3] if !s1.is_empty() && !s2.is_empty() && s3.is_empty() => { |
322 | 0 | let b1 = s1.as_bytes()[0]; |
323 | 0 | let b2 = s2.as_bytes()[0]; |
324 | 0 | let b3 = s2.as_bytes()[0]; |
325 | 0 | let miter = |
326 | 0 | memchr::memchr3_iter(b1, b2, b3, &self.input.as_bytes()[self.pos..]); |
327 | 0 | for from in miter { |
328 | 0 | let start = &self.input[self.pos + from..]; |
329 | 0 | if start.starts_with(s1) || start.starts_with(s2) || start.starts_with(s3) { |
330 | 0 | self.pos += from; |
331 | 0 | return true; |
332 | 0 | } |
333 | | } |
334 | | } |
335 | | _ => { |
336 | 0 | return self.skip_until_basic(strings); |
337 | | } |
338 | | } |
339 | 0 | self.pos = self.input.len(); |
340 | 0 | false |
341 | | } |
342 | 0 | } |
343 | | |
344 | | #[inline] |
345 | 0 | fn skip_until_basic(&mut self, strings: &[&str]) -> bool { |
346 | | // TODO: optimize with Aho-Corasick, e.g. https://crates.io/crates/daachorse? |
347 | 0 | for from in self.pos..self.input.len() { |
348 | 0 | let bytes = if let Some(string) = self.input.get(from..) { |
349 | 0 | string.as_bytes() |
350 | | } else { |
351 | 0 | continue; |
352 | | }; |
353 | | |
354 | 0 | for slice in strings.iter() { |
355 | 0 | let to = slice.len(); |
356 | 0 | if Some(slice.as_bytes()) == bytes.get(0..to) { |
357 | 0 | self.pos = from; |
358 | 0 | return true; |
359 | 0 | } |
360 | | } |
361 | | } |
362 | | |
363 | 0 | self.pos = self.input.len(); |
364 | 0 | false |
365 | 0 | } |
366 | | |
367 | | /// Matches the char at the `Position` against a specified character and returns `true` if a match |
368 | | /// was made. If no match was made, returns `false`. |
369 | | /// `pos` will not be updated in either case. |
370 | | #[inline] |
371 | 0 | pub(crate) fn match_char(&self, c: char) -> bool { |
372 | 0 | matches!(self.input[self.pos..].chars().next(), Some(cc) if c == cc) |
373 | 0 | } Unexecuted instantiation: <pest::position::Position>::match_char Unexecuted instantiation: <pest::position::Position>::match_char |
374 | | |
375 | | /// Matches the char at the `Position` against a filter function and returns `true` if a match |
376 | | /// was made. If no match was made, returns `false` and `pos` will not be updated. |
377 | | #[inline] |
378 | 0 | pub(crate) fn match_char_by<F>(&mut self, f: F) -> bool |
379 | 0 | where |
380 | 0 | F: FnOnce(char) -> bool, |
381 | 0 | { |
382 | 0 | if let Some(c) = self.input[self.pos..].chars().next() { |
383 | 0 | if f(c) { |
384 | 0 | self.pos += c.len_utf8(); |
385 | 0 | true |
386 | | } else { |
387 | 0 | false |
388 | | } |
389 | | } else { |
390 | 0 | false |
391 | | } |
392 | 0 | } |
393 | | |
394 | | /// Matches `string` from the `Position` and returns `true` if a match was made or `false` |
395 | | /// otherwise. If no match was made, `pos` will not be updated. |
396 | | #[inline] |
397 | 0 | pub(crate) fn match_string(&mut self, string: &str) -> bool { |
398 | 0 | let to = self.pos + string.len(); |
399 | 0 |
|
400 | 0 | if Some(string.as_bytes()) == self.input.as_bytes().get(self.pos..to) { |
401 | 0 | self.pos = to; |
402 | 0 | true |
403 | | } else { |
404 | 0 | false |
405 | | } |
406 | 0 | } Unexecuted instantiation: <pest::position::Position>::match_string Unexecuted instantiation: <pest::position::Position>::match_string |
407 | | |
408 | | /// Case-insensitively matches `string` from the `Position` and returns `true` if a match was |
409 | | /// made or `false` otherwise. If no match was made, `pos` will not be updated. |
410 | | #[inline] |
411 | 0 | pub(crate) fn match_insensitive(&mut self, string: &str) -> bool { |
412 | 0 | let matched = { |
413 | 0 | let slice = &self.input[self.pos..]; |
414 | 0 | if let Some(slice) = slice.get(0..string.len()) { |
415 | 0 | slice.eq_ignore_ascii_case(string) |
416 | | } else { |
417 | 0 | false |
418 | | } |
419 | | }; |
420 | | |
421 | 0 | if matched { |
422 | 0 | self.pos += string.len(); |
423 | 0 | true |
424 | | } else { |
425 | 0 | false |
426 | | } |
427 | 0 | } |
428 | | |
429 | | /// Matches `char` `range` from the `Position` and returns `true` if a match was made or `false` |
430 | | /// otherwise. If no match was made, `pos` will not be updated. |
431 | | #[inline] |
432 | 0 | pub(crate) fn match_range(&mut self, range: Range<char>) -> bool { |
433 | 0 | if let Some(c) = self.input[self.pos..].chars().next() { |
434 | 0 | if range.start <= c && c <= range.end { |
435 | 0 | self.pos += c.len_utf8(); |
436 | 0 | return true; |
437 | 0 | } |
438 | 0 | } |
439 | | |
440 | 0 | false |
441 | 0 | } Unexecuted instantiation: <pest::position::Position>::match_range Unexecuted instantiation: <pest::position::Position>::match_range |
442 | | } |
443 | | |
444 | | impl<'i> fmt::Debug for Position<'i> { |
445 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
446 | 0 | f.debug_struct("Position").field("pos", &self.pos).finish() |
447 | 0 | } |
448 | | } |
449 | | |
450 | | impl<'i> PartialEq for Position<'i> { |
451 | 0 | fn eq(&self, other: &Position<'i>) -> bool { |
452 | 0 | ptr::eq(self.input, other.input) && self.pos == other.pos |
453 | 0 | } |
454 | | } |
455 | | |
456 | | impl<'i> Eq for Position<'i> {} |
457 | | |
458 | | #[allow(clippy::non_canonical_partial_ord_impl)] |
459 | | impl<'i> PartialOrd for Position<'i> { |
460 | 0 | fn partial_cmp(&self, other: &Position<'i>) -> Option<Ordering> { |
461 | 0 | if ptr::eq(self.input, other.input) { |
462 | 0 | self.pos.partial_cmp(&other.pos) |
463 | | } else { |
464 | 0 | None |
465 | | } |
466 | 0 | } |
467 | | } |
468 | | |
469 | | impl<'i> Ord for Position<'i> { |
470 | 0 | fn cmp(&self, other: &Position<'i>) -> Ordering { |
471 | 0 | self.partial_cmp(other) |
472 | 0 | .expect("cannot compare positions from different strs") |
473 | 0 | } |
474 | | } |
475 | | |
476 | | impl<'i> Hash for Position<'i> { |
477 | 0 | fn hash<H: Hasher>(&self, state: &mut H) { |
478 | 0 | (self.input as *const str).hash(state); |
479 | 0 | self.pos.hash(state); |
480 | 0 | } |
481 | | } |
482 | | |
483 | | #[cfg(test)] |
484 | | mod tests { |
485 | | use super::*; |
486 | | |
487 | | #[test] |
488 | | fn empty() { |
489 | | let input = ""; |
490 | | assert!(Position::new(input, 0).unwrap().match_string("")); |
491 | | assert!(!Position::new(input, 0).unwrap().match_string("a")); |
492 | | } |
493 | | |
494 | | #[test] |
495 | | fn parts() { |
496 | | let input = "asdasdf"; |
497 | | |
498 | | assert!(Position::new(input, 0).unwrap().match_string("asd")); |
499 | | assert!(Position::new(input, 3).unwrap().match_string("asdf")); |
500 | | } |
501 | | |
502 | | #[test] |
503 | | fn line_col() { |
504 | | let input = "a\rb\nc\r\nd嗨"; |
505 | | |
506 | | assert_eq!(Position::new(input, 0).unwrap().line_col(), (1, 1)); |
507 | | assert_eq!(Position::new(input, 1).unwrap().line_col(), (1, 2)); |
508 | | assert_eq!(Position::new(input, 2).unwrap().line_col(), (1, 3)); |
509 | | assert_eq!(Position::new(input, 3).unwrap().line_col(), (1, 4)); |
510 | | assert_eq!(Position::new(input, 4).unwrap().line_col(), (2, 1)); |
511 | | assert_eq!(Position::new(input, 5).unwrap().line_col(), (2, 2)); |
512 | | assert_eq!(Position::new(input, 6).unwrap().line_col(), (2, 3)); |
513 | | assert_eq!(Position::new(input, 7).unwrap().line_col(), (3, 1)); |
514 | | assert_eq!(Position::new(input, 8).unwrap().line_col(), (3, 2)); |
515 | | assert_eq!(Position::new(input, 11).unwrap().line_col(), (3, 3)); |
516 | | let input = "abcd嗨"; |
517 | | assert_eq!(Position::new(input, 7).unwrap().line_col(), (1, 6)); |
518 | | } |
519 | | |
520 | | #[test] |
521 | | fn line_of() { |
522 | | let input = "a\rb\nc\r\nd嗨"; |
523 | | |
524 | | assert_eq!(Position::new(input, 0).unwrap().line_of(), "a\rb\n"); |
525 | | assert_eq!(Position::new(input, 1).unwrap().line_of(), "a\rb\n"); |
526 | | assert_eq!(Position::new(input, 2).unwrap().line_of(), "a\rb\n"); |
527 | | assert_eq!(Position::new(input, 3).unwrap().line_of(), "a\rb\n"); |
528 | | assert_eq!(Position::new(input, 4).unwrap().line_of(), "c\r\n"); |
529 | | assert_eq!(Position::new(input, 5).unwrap().line_of(), "c\r\n"); |
530 | | assert_eq!(Position::new(input, 6).unwrap().line_of(), "c\r\n"); |
531 | | assert_eq!(Position::new(input, 7).unwrap().line_of(), "d嗨"); |
532 | | assert_eq!(Position::new(input, 8).unwrap().line_of(), "d嗨"); |
533 | | assert_eq!(Position::new(input, 11).unwrap().line_of(), "d嗨"); |
534 | | } |
535 | | |
536 | | #[test] |
537 | | fn line_of_empty() { |
538 | | let input = ""; |
539 | | |
540 | | assert_eq!(Position::new(input, 0).unwrap().line_of(), ""); |
541 | | } |
542 | | |
543 | | #[test] |
544 | | fn line_of_new_line() { |
545 | | let input = "\n"; |
546 | | |
547 | | assert_eq!(Position::new(input, 0).unwrap().line_of(), "\n"); |
548 | | } |
549 | | |
550 | | #[test] |
551 | | fn line_of_between_new_line() { |
552 | | let input = "\n\n"; |
553 | | |
554 | | assert_eq!(Position::new(input, 1).unwrap().line_of(), "\n"); |
555 | | } |
556 | | |
557 | | fn measure_skip(input: &str, pos: usize, n: usize) -> Option<usize> { |
558 | | let mut p = Position::new(input, pos).unwrap(); |
559 | | if p.skip(n) { |
560 | | Some(p.pos - pos) |
561 | | } else { |
562 | | None |
563 | | } |
564 | | } |
565 | | |
566 | | #[test] |
567 | | fn skip_empty() { |
568 | | let input = ""; |
569 | | |
570 | | assert_eq!(measure_skip(input, 0, 0), Some(0)); |
571 | | assert_eq!(measure_skip(input, 0, 1), None); |
572 | | } |
573 | | |
574 | | #[test] |
575 | | fn skip() { |
576 | | let input = "d嗨"; |
577 | | |
578 | | assert_eq!(measure_skip(input, 0, 0), Some(0)); |
579 | | assert_eq!(measure_skip(input, 0, 1), Some(1)); |
580 | | assert_eq!(measure_skip(input, 1, 1), Some(3)); |
581 | | } |
582 | | |
583 | | #[test] |
584 | | fn skip_until() { |
585 | | let input = "ab ac"; |
586 | | let pos = Position::from_start(input); |
587 | | |
588 | | let mut test_pos = pos; |
589 | | test_pos.skip_until(&["a", "b"]); |
590 | | assert_eq!(test_pos.pos(), 0); |
591 | | |
592 | | test_pos = pos; |
593 | | test_pos.skip_until(&["b"]); |
594 | | assert_eq!(test_pos.pos(), 1); |
595 | | |
596 | | test_pos = pos; |
597 | | test_pos.skip_until(&["ab"]); |
598 | | assert_eq!(test_pos.pos(), 0); |
599 | | |
600 | | test_pos = pos; |
601 | | test_pos.skip_until(&["ac", "z"]); |
602 | | assert_eq!(test_pos.pos(), 3); |
603 | | |
604 | | test_pos = pos; |
605 | | assert!(!test_pos.skip_until(&["z"])); |
606 | | assert_eq!(test_pos.pos(), 5); |
607 | | } |
608 | | |
609 | | #[test] |
610 | | fn match_range() { |
611 | | let input = "b"; |
612 | | |
613 | | assert!(Position::new(input, 0).unwrap().match_range('a'..'c')); |
614 | | assert!(Position::new(input, 0).unwrap().match_range('b'..'b')); |
615 | | assert!(!Position::new(input, 0).unwrap().match_range('a'..'a')); |
616 | | assert!(!Position::new(input, 0).unwrap().match_range('c'..'c')); |
617 | | assert!(Position::new(input, 0).unwrap().match_range('a'..'嗨')); |
618 | | } |
619 | | |
620 | | #[test] |
621 | | fn match_insensitive() { |
622 | | let input = "AsdASdF"; |
623 | | |
624 | | assert!(Position::new(input, 0).unwrap().match_insensitive("asd")); |
625 | | assert!(Position::new(input, 3).unwrap().match_insensitive("asdf")); |
626 | | } |
627 | | |
628 | | #[test] |
629 | | fn cmp() { |
630 | | let input = "a"; |
631 | | let start = Position::from_start(input); |
632 | | let mut end = start; |
633 | | |
634 | | assert!(end.skip(1)); |
635 | | let result = start.cmp(&end); |
636 | | |
637 | | assert_eq!(result, Ordering::Less); |
638 | | } |
639 | | |
640 | | #[test] |
641 | | #[should_panic] |
642 | | fn cmp_panic() { |
643 | | let input1 = "a"; |
644 | | let input2 = "b"; |
645 | | let pos1 = Position::from_start(input1); |
646 | | let pos2 = Position::from_start(input2); |
647 | | |
648 | | let _ = pos1.cmp(&pos2); |
649 | | } |
650 | | |
651 | | #[test] |
652 | | #[cfg(feature = "std")] |
653 | | fn hash() { |
654 | | use std::collections::HashSet; |
655 | | |
656 | | let input = "a"; |
657 | | let start = Position::from_start(input); |
658 | | let mut positions = HashSet::new(); |
659 | | |
660 | | positions.insert(start); |
661 | | } |
662 | | } |