pest_typed/span.rs
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//! Copied from pest/pest/src/position.rs (commit ac0aed3eecf435fd93ba575a39704aaa88a375b7)
11//! and modified.
12
13use crate::{formatter::FormatOption, line_indexer::LineIndexer, position};
14use core::{
15 fmt::{self, Write},
16 hash::{Hash, Hasher},
17 ops::{Bound, Range, RangeBounds},
18 ptr, str,
19};
20
21/// A span over a `&str`. It is created from either [two `Position`s] or from a [`Pair`].
22///
23/// [two `Position`s]: struct.Position.html#method.span
24/// [`Pair`]: ../iterators/struct.Pair.html#method.span
25#[derive(Clone, Copy)]
26pub struct Span<'i> {
27 input: &'i str,
28 /// # Safety
29 ///
30 /// Must be a valid character boundary index into `input`.
31 start: usize,
32 /// # Safety
33 ///
34 /// Must be a valid character boundary index into `input`.
35 end: usize,
36}
37
38impl<'i> Span<'i> {
39 /// Create a new `Span` without checking invariants. (Checked with `debug_assertions`.)
40 ///
41 /// # Safety
42 ///
43 /// `input[start..end]` must be a valid subslice; that is, said indexing should not panic.
44 pub(crate) unsafe fn new_unchecked(input: &'i str, start: usize, end: usize) -> Self {
45 debug_assert!(input.get(start..end).is_some());
46 Span { input, start, end }
47 }
48
49 /// Create a new span that contains the entire input.
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// # use pest_typed::Span;
55 /// let input = "Hello!";
56 /// let span = Span::new_full(input);
57 /// assert_eq!(span.as_str(), input);
58 /// ```
59 pub const fn new_full(input: &'i str) -> Self {
60 Span {
61 input,
62 start: 0,
63 end: input.len(),
64 }
65 }
66
67 /// Create a new span that points to the end of the input.
68 ///
69 /// # Examples
70 ///
71 /// ```
72 /// # use pest_typed::Span;
73 /// let input = "Hello!";
74 /// let span = Span::new_at_end(input);
75 /// assert_eq!(span.as_str(), "");
76 /// ```
77 pub const fn new_at_end(input: &'i str) -> Self {
78 Span {
79 input,
80 start: input.len(),
81 end: input.len(),
82 }
83 }
84
85 /// Attempts to create a new span. Will return `None` if `input[start..end]` is an invalid index
86 /// into `input`.
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// # use pest::Span;
92 /// let input = "Hello!";
93 /// assert_eq!(None, Span::new(input, 100, 0));
94 /// assert!(Span::new(input, 0, input.len()).is_some());
95 /// ```
96 pub fn new(input: &'i str, start: usize, end: usize) -> Option<Self> {
97 if input.get(start..end).is_some() {
98 Some(Span { input, start, end })
99 } else {
100 None
101 }
102 }
103
104 /// Attempts to create a new span based on a sub-range.
105 ///
106 /// ```
107 /// use pest::Span;
108 /// let input = "Hello World!";
109 /// let world = Span::new(input, 6, input.len()).unwrap();
110 /// let orl = world.get(1..=3);
111 /// assert!(orl.is_some());
112 /// assert_eq!(orl.unwrap().as_str(), "orl");
113 /// ```
114 ///
115 /// # Examples
116 pub fn get(&self, range: impl RangeBounds<usize>) -> Option<Self> {
117 let start = match range.start_bound() {
118 Bound::Included(offset) => *offset,
119 Bound::Excluded(offset) => *offset + 1,
120 Bound::Unbounded => 0,
121 };
122 let end = match range.end_bound() {
123 Bound::Included(offset) => *offset + 1,
124 Bound::Excluded(offset) => *offset,
125 Bound::Unbounded => self.as_str().len(),
126 };
127
128 self.as_str().get(start..end).map(|_| Span {
129 input: self.input,
130 start: self.start + start,
131 end: self.start + end,
132 })
133 }
134
135 /// Returns the `Span`'s start byte position as a `usize`.
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// # use pest::Position;
141 /// let input = "ab";
142 /// let start = Position::from_start(input);
143 /// let end = start.clone();
144 /// let span = start.span(&end);
145 ///
146 /// assert_eq!(span.start(), 0);
147 /// ```
148 #[inline]
149 pub const fn start(&self) -> usize {
150 self.start
151 }
152
153 /// Returns the `Span`'s end byte position as a `usize`.
154 ///
155 /// # Examples
156 ///
157 /// ```
158 /// # use pest::Position;
159 /// let input = "ab";
160 /// let start = Position::from_start(input);
161 /// let end = start.clone();
162 /// let span = start.span(&end);
163 ///
164 /// assert_eq!(span.end(), 0);
165 /// ```
166 #[inline]
167 pub const fn end(&self) -> usize {
168 self.end
169 }
170
171 /// Returns the `Span`'s start `Position`.
172 ///
173 /// # Examples
174 ///
175 /// ```
176 /// # use pest::Position;
177 /// let input = "ab";
178 /// let start = Position::from_start(input);
179 /// let end = start.clone();
180 /// let span = start.clone().span(&end);
181 ///
182 /// assert_eq!(span.start_pos(), start);
183 /// ```
184 #[inline]
185 pub fn start_pos(&self) -> position::Position<'i> {
186 // Span's start position is always a UTF-8 border.
187 unsafe { position::Position::new_unchecked(self.input, self.start) }
188 }
189
190 /// Returns the `Span`'s end `Position`.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// # use pest::Position;
196 /// let input = "ab";
197 /// let start = Position::from_start(input);
198 /// let end = start.clone();
199 /// let span = start.span(&end);
200 ///
201 /// assert_eq!(span.end_pos(), end);
202 /// ```
203 #[inline]
204 pub fn end_pos(&self) -> position::Position<'i> {
205 // Span's end position is always a UTF-8 border.
206 unsafe { position::Position::new_unchecked(self.input, self.end) }
207 }
208
209 /// Splits the `Span` into a pair of `Position`s.
210 ///
211 /// # Examples
212 ///
213 /// ```
214 /// # use pest::Position;
215 /// let input = "ab";
216 /// let start = Position::from_start(input);
217 /// let end = start.clone();
218 /// let span = start.clone().span(&end);
219 ///
220 /// assert_eq!(span.split(), (start, end));
221 /// ```
222 #[inline]
223 pub fn split(self) -> (position::Position<'i>, position::Position<'i>) {
224 // Span's start and end positions are always a UTF-8 borders.
225 let pos1 = unsafe { position::Position::new_unchecked(self.input, self.start) };
226 let pos2 = unsafe { position::Position::new_unchecked(self.input, self.end) };
227
228 (pos1, pos2)
229 }
230
231 /// Captures a slice from the `&str` defined by the `Span`.
232 ///
233 /// # Examples
234 ///
235 /// ```
236 /// # use pest;
237 /// # #[allow(non_camel_case_types)]
238 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
239 /// enum Rule {}
240 ///
241 /// let input = "abc";
242 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input).skip(1).unwrap();
243 /// let start_pos = state.position().clone();
244 /// state = state.match_string("b").unwrap();
245 /// let span = start_pos.span(&state.position().clone());
246 /// assert_eq!(span.as_str(), "b");
247 /// ```
248 #[inline]
249 pub fn as_str(&self) -> &'i str {
250 // Span's start and end positions are always a UTF-8 borders.
251 &self.input[self.start..self.end]
252 }
253
254 /// Returns the input string of the `Span`.
255 ///
256 /// This function returns the input string of the `Span` as a `&str`. This is the source string
257 /// from which the `Span` was created. The returned `&str` can be used to examine the contents of
258 /// the `Span` or to perform further processing on the string.
259 ///
260 /// # Examples
261 ///
262 /// ```
263 /// # use pest;
264 /// # use pest::Span;
265 ///
266 /// // Example: Get input string from a span
267 /// let input = "abc\ndef\nghi";
268 /// let span = Span::new(input, 1, 7).unwrap();
269 /// assert_eq!(span.get_input(), input);
270 /// ```
271 pub const fn get_input(&self) -> &'i str {
272 self.input
273 }
274
275 /// Iterates over all lines (partially) covered by this span. Yielding a `&str` for each line.
276 ///
277 /// # Examples
278 ///
279 /// ```
280 /// # use pest;
281 /// # #[allow(non_camel_case_types)]
282 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
283 /// enum Rule {}
284 ///
285 /// let input = "a\nb\nc";
286 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input).skip(2).unwrap();
287 /// let start_pos = state.position().clone();
288 /// state = state.match_string("b\nc").unwrap();
289 /// let span = start_pos.span(&state.position().clone());
290 /// assert_eq!(span.lines().collect::<Vec<_>>(), vec!["b\n", "c"]);
291 /// ```
292 #[inline]
293 pub const fn lines<L: LineIndexer<'i>>(&self, indexer: L) -> Lines<'_, 'i, L> {
294 Lines {
295 inner: self.lines_span(indexer),
296 }
297 }
298
299 /// Iterates over all lines (partially) covered by this span. Yielding a `Span` for each line.
300 ///
301 /// # Examples
302 ///
303 /// ```
304 /// # use pest;
305 /// # use pest::Span;
306 /// # #[allow(non_camel_case_types)]
307 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
308 /// enum Rule {}
309 ///
310 /// let input = "a\nb\nc";
311 /// let mut state: Box<pest::ParserState<'_, Rule>> = pest::ParserState::new(input).skip(2).unwrap();
312 /// let start_pos = state.position().clone();
313 /// state = state.match_string("b\nc").unwrap();
314 /// let span = start_pos.span(&state.position().clone());
315 /// assert_eq!(span.lines_span().collect::<Vec<_>>(), vec![Span::new(input, 2, 4).unwrap(), Span::new(input, 4, 5).unwrap()]);
316 /// ```
317 pub const fn lines_span<L: LineIndexer<'i>>(&self, indexer: L) -> LinesSpan<'_, 'i, L> {
318 LinesSpan {
319 span: self,
320 indexer,
321 pos: self.start,
322 }
323 }
324
325 /// Skips `n` `char`s from the `Span` and returns `true` if the skip was possible or `false`
326 /// otherwise. If the return value is `false`, `start` will not be updated.
327 #[inline]
328 #[allow(dead_code)]
329 pub(crate) fn skip(&mut self, n: usize) -> bool {
330 let skipped = {
331 let mut len = 0;
332 // Position's pos is always a UTF-8 border.
333 let mut chars = self.input[self.start..self.end].chars();
334 for _ in 0..n {
335 if let Some(c) = chars.next() {
336 len += c.len_utf8();
337 } else {
338 return false;
339 }
340 }
341 len
342 };
343
344 self.start += skipped;
345 true
346 }
347
348 /// Skips until one of the given `strings` is found. If none of the `strings` can be found,
349 /// this function will return `false` but its `pos` will *still* be updated.
350 #[inline]
351 #[allow(dead_code, unexpected_cfgs)]
352 pub(crate) fn skip_until(&mut self, strings: &[&str]) -> bool {
353 #[cfg(not(feature = "memchr"))]
354 {
355 self.skip_until_basic(strings)
356 }
357 #[cfg(feature = "memchr")]
358 {
359 match strings {
360 [] => (),
361 [s1] => {
362 if let Some(from) = memchr::memmem::find(
363 &self.input.as_bytes()[self.start..self.end],
364 s1.as_bytes(),
365 ) {
366 self.start += from;
367 return true;
368 }
369 }
370 [s1, s2] if !s1.is_empty() && !s2.is_empty() => {
371 let b1 = s1.as_bytes()[0];
372 let b2 = s2.as_bytes()[0];
373 let miter = memchr::memchr2_iter(b1, b2, &self.input.as_bytes()[self.pos..]);
374 for from in miter {
375 let start = &self.input[self.pos + from..];
376 if start.starts_with(s1) || start.starts_with(s2) {
377 self.pos += from;
378 return true;
379 }
380 }
381 }
382 [s1, s2, s3] if !s1.is_empty() && !s2.is_empty() && s3.is_empty() => {
383 let b1 = s1.as_bytes()[0];
384 let b2 = s2.as_bytes()[0];
385 let b3 = s2.as_bytes()[0];
386 let miter =
387 memchr::memchr3_iter(b1, b2, b3, &self.input.as_bytes()[self.pos..]);
388 for from in miter {
389 let start = &self.input[self.pos + from..];
390 if start.starts_with(s1) || start.starts_with(s2) || start.starts_with(s3) {
391 self.pos += from;
392 return true;
393 }
394 }
395 }
396 _ => {
397 return self.skip_until_basic(strings);
398 }
399 }
400 self.pos = self.input.len();
401 false
402 }
403 }
404
405 #[inline]
406 fn skip_until_basic(&mut self, strings: &[&str]) -> bool {
407 // TODO: optimize with Aho-Corasick, e.g. https://crates.io/crates/daachorse?
408 for from in self.start..self.end {
409 let bytes = if let Some(string) = self.input.get(from..) {
410 string.as_bytes()
411 } else {
412 continue;
413 };
414
415 for slice in strings.iter() {
416 let to = slice.len();
417 if Some(slice.as_bytes()) == bytes.get(0..to) {
418 self.start = from;
419 return true;
420 }
421 }
422 }
423
424 self.start = self.end;
425 false
426 }
427
428 /// Matches the char at the `Position` against a specified character and returns `true` if a match
429 /// was made. If no match was made, returns `false`.
430 /// `pos` will not be updated in either case.
431 #[inline]
432 #[allow(dead_code)]
433 pub(crate) fn match_char(&self, c: char) -> bool {
434 matches!(self.input[self.start..self.end].chars().next(), Some(cc) if c == cc)
435 }
436
437 /// Matches the char at the `Position` against a filter function and returns `true` if a match
438 /// was made. If no match was made, returns `false` and `pos` will not be updated.
439 #[inline]
440 #[allow(dead_code)]
441 pub(crate) fn match_char_by<F>(&mut self, f: F) -> bool
442 where
443 F: FnOnce(char) -> bool,
444 {
445 if let Some(c) = self.input[self.start..self.end].chars().next() {
446 if f(c) {
447 self.start += c.len_utf8();
448 true
449 } else {
450 false
451 }
452 } else {
453 false
454 }
455 }
456
457 /// Matches `string` from the `Position` and returns `true` if a match was made or `false`
458 /// otherwise. If no match was made, `pos` will not be updated.
459 #[inline]
460 #[allow(dead_code)]
461 pub(crate) fn match_string(&mut self, string: &str) -> bool {
462 let to = self.start + string.len();
463
464 if self.end < to {
465 false
466 } else if Some(string.as_bytes()) == self.input.as_bytes().get(self.start..to) {
467 self.start = to;
468 true
469 } else {
470 false
471 }
472 }
473
474 /// Case-insensitively matches `string` from the `Position` and returns `true` if a match was
475 /// made or `false` otherwise. If no match was made, `pos` will not be updated.
476 #[inline]
477 #[allow(dead_code)]
478 pub(crate) fn match_insensitive(&mut self, string: &str) -> bool {
479 let matched = {
480 let slice = &self.input[self.start..self.end];
481 if let Some(slice) = slice.get(0..string.len()) {
482 slice.eq_ignore_ascii_case(string)
483 } else {
484 false
485 }
486 };
487
488 if matched {
489 self.start += string.len();
490 true
491 } else {
492 false
493 }
494 }
495
496 /// Matches `char` `range` from the `Position` and returns `true` if a match was made or `false`
497 /// otherwise. If no match was made, `pos` will not be updated.
498 #[inline]
499 #[allow(dead_code)]
500 pub(crate) fn match_range(&mut self, range: Range<char>) -> bool {
501 if let Some(c) = self.input[self.start..self.end].chars().next() {
502 if range.start <= c && c <= range.end {
503 self.start += c.len_utf8();
504 return true;
505 }
506 }
507
508 false
509 }
510}
511
512impl fmt::Debug for Span<'_> {
513 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514 f.debug_struct("Span")
515 .field("str", &self.as_str())
516 .field("start", &self.start)
517 .field("end", &self.end)
518 .finish()
519 }
520}
521
522impl PartialEq for Span<'_> {
523 fn eq(&self, other: &Self) -> bool {
524 ptr::eq::<str>(self.input, other.input)
525 && self.start == other.start
526 && self.end == other.end
527 }
528}
529
530impl Eq for Span<'_> {}
531
532impl Hash for Span<'_> {
533 fn hash<H: Hasher>(&self, state: &mut H) {
534 (self.input as *const str).hash(state);
535 self.start.hash(state);
536 self.end.hash(state);
537 }
538}
539
540impl<'i> Span<'i> {
541 /// Format span with given option.
542 #[inline]
543 pub fn display<L, Writer, SF, MF, NF>(
544 &self,
545 indexer: L,
546 f: &mut Writer,
547 opt: FormatOption<SF, MF, NF>,
548 ) -> fmt::Result
549 where
550 L: LineIndexer<'i>,
551 Writer: Write,
552 SF: FnMut(&str, &mut Writer) -> fmt::Result,
553 MF: FnMut(&str, &mut Writer) -> fmt::Result,
554 NF: FnMut(&str, &mut Writer) -> fmt::Result,
555 {
556 opt.display_span(self, indexer, f)
557 }
558}
559
560impl fmt::Display for Span<'_> {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 let opt = FormatOption::default();
563 opt.display_span(self, (), f)
564 }
565}
566
567/// Merges two spans into one.
568///
569/// This function merges two spans that are contiguous or overlapping into a single span
570/// that covers the entire range of the two input spans. This is useful when you want to
571/// aggregate information from multiple spans into a single entity.
572///
573/// The function checks if the input spans are overlapping or contiguous by comparing their
574/// start and end positions. If they are, a new span is created with the minimum start position
575/// and the maximum end position of the two input spans.
576///
577/// If the input spans are neither overlapping nor contiguous, the function returns None,
578/// indicating that a merge operation was not possible.
579///
580/// # Examples
581///
582/// ```
583/// # use pest;
584/// # use pest::Span;
585/// # use pest::merge_spans;
586///
587/// // Example 1: Contiguous spans
588/// let input = "abc\ndef\nghi";
589/// let span1 = Span::new(input, 1, 7).unwrap();
590/// let span2 = Span::new(input, 7, 11).unwrap();
591/// let merged = merge_spans(&span1, &span2).unwrap();
592/// assert_eq!(merged, Span::new(input, 1, 11).unwrap());
593///
594/// // Example 2: Overlapping spans
595/// let input = "abc\ndef\nghi";
596/// let span1 = Span::new(input, 1, 7).unwrap();
597/// let span2 = Span::new(input, 5, 11).unwrap();
598/// let merged = merge_spans(&span1, &span2).unwrap();
599/// assert_eq!(merged, Span::new(input, 1, 11).unwrap());
600///
601/// // Example 3: Non-contiguous spans
602/// let input = "abc\ndef\nghi";
603/// let span1 = Span::new(input, 1, 7).unwrap();
604/// let span2 = Span::new(input, 8, 11).unwrap();
605/// let merged = merge_spans(&span1, &span2);
606/// assert!(merged.is_none());
607/// ```
608pub fn merge_spans<'i>(a: &Span<'i>, b: &Span<'i>) -> Option<Span<'i>> {
609 if a.end() >= b.start() && a.start() <= b.end() {
610 // The spans overlap or are contiguous, so they can be merged.
611 Span::new(
612 a.get_input(),
613 core::cmp::min(a.start(), b.start()),
614 core::cmp::max(a.end(), b.end()),
615 )
616 } else {
617 // The spans don't overlap and aren't contiguous, so they can't be merged.
618 None
619 }
620}
621
622/// Line iterator for Spans, created by [`Span::lines_span()`].
623///
624/// Iterates all lines that are at least _partially_ covered by the span. Yielding a `Span` for each.
625///
626/// [`Span::lines_span()`]: struct.Span.html#method.lines_span
627pub struct LinesSpan<'s, 'i, L> {
628 span: &'s Span<'i>,
629 indexer: L,
630 pos: usize,
631}
632
633impl<'i, L: LineIndexer<'i>> Iterator for LinesSpan<'_, 'i, L> {
634 type Item = Span<'i>;
635 fn next(&mut self) -> Option<Self::Item> {
636 if self.pos > self.span.end {
637 return None;
638 }
639 let pos = position::Position::new(self.span.input, self.pos)?;
640 if pos.at_end() {
641 return None;
642 }
643
644 let line_start = pos.find_line_start(&self.indexer);
645 self.pos = pos.find_line_end(&self.indexer);
646
647 Span::new(self.span.input, line_start, self.pos)
648 }
649}
650
651/// Line iterator for Spans, created by [`Span::lines()`].
652///
653/// Iterates all lines that are at least _partially_ covered by the span. Yielding a `&str` for each.
654///
655/// [`Span::lines()`]: struct.Span.html#method.lines
656pub struct Lines<'s, 'i, L> {
657 inner: LinesSpan<'s, 'i, L>,
658}
659
660impl<'i, L: LineIndexer<'i>> Iterator for Lines<'_, 'i, L> {
661 type Item = &'i str;
662 fn next(&mut self) -> Option<Self::Item> {
663 self.inner.next().map(|span| span.as_str())
664 }
665}
666
667#[cfg(test)]
668mod tests {
669 use super::*;
670 use alloc::{borrow::ToOwned, vec::Vec};
671
672 #[test]
673 fn get() {
674 let input = "abc123abc";
675 let span = Span::new(input, 3, input.len()).unwrap();
676 assert_eq!(span.as_str(), "123abc");
677 assert_eq!(span.input, input);
678
679 let span1 = span.get(..=2);
680 assert!(span1.is_some());
681 assert_eq!(span1.unwrap().input, input);
682 assert_eq!(span1.unwrap().as_str(), "123");
683
684 let span2 = span.get(..);
685 assert!(span2.is_some());
686 assert_eq!(span2.unwrap().input, input);
687 assert_eq!(span2.unwrap().as_str(), "123abc");
688
689 let span3 = span.get(3..);
690 assert!(span3.is_some());
691 assert_eq!(span3.unwrap().input, input);
692 assert_eq!(span3.unwrap().as_str(), "abc");
693
694 let span4 = span.get(0..0);
695 assert!(span4.is_some());
696 assert_eq!(span4.unwrap().input, input);
697 assert_eq!(span4.unwrap().as_str(), "");
698 }
699
700 #[test]
701 fn get_fails() {
702 let input = "abc";
703 let span = Span::new(input, 0, input.len()).unwrap();
704
705 let span1 = span.get(0..100);
706 assert!(span1.is_none());
707
708 let span2 = span.get(100..200);
709 assert!(span2.is_none());
710 }
711
712 #[test]
713 fn span_comp() {
714 let input = "abc\ndef\nghi";
715 let span = Span::new(input, 1, 7).unwrap();
716 let span2 = Span::new(input, 50, 51);
717 assert!(span2.is_none());
718 let span3 = Span::new(input, 0, 8).unwrap();
719 assert!(span != span3);
720 }
721
722 #[test]
723 fn split() {
724 let input = "a";
725 let start = position::Position::from_start(input);
726 let mut end = start;
727
728 assert!(end.skip(1));
729
730 let span = start.clone().span(&end.clone());
731
732 assert_eq!(span.split(), (start, end));
733 }
734
735 #[test]
736 fn lines_mid() {
737 let input = "abc\ndef\nghi";
738 let span = Span::new(input, 1, 7).unwrap();
739 let lines: Vec<_> = span.lines(()).collect();
740 let lines_span: Vec<_> = span.lines_span(()).map(|span| span.as_str()).collect();
741
742 assert_eq!(lines.len(), 2);
743 assert_eq!(lines[0], "abc\n".to_owned());
744 assert_eq!(lines[1], "def\n".to_owned());
745 assert_eq!(lines, lines_span) // Verify parity with lines_span()
746 }
747
748 #[test]
749 fn lines_eof() {
750 let input = "abc\ndef\nghi";
751 let span = Span::new(input, 5, 11).unwrap();
752 assert!(span.end_pos().at_end());
753 assert_eq!(span.end(), 11);
754 let lines: Vec<_> = span.lines(()).collect();
755 let lines_span: Vec<_> = span.lines_span(()).map(|span| span.as_str()).collect();
756
757 assert_eq!(lines.len(), 2);
758 assert_eq!(lines[0], "def\n".to_owned());
759 assert_eq!(lines[1], "ghi".to_owned());
760 assert_eq!(lines, lines_span) // Verify parity with lines_span()
761 }
762
763 #[test]
764 fn lines_span() {
765 let input = "abc\ndef\nghi";
766 let span = Span::new(input, 1, 7).unwrap();
767 let lines_span: Vec<_> = span.lines_span(()).collect();
768 let lines: Vec<_> = span.lines(()).collect();
769
770 assert_eq!(lines_span.len(), 2);
771 assert_eq!(lines_span[0], Span::new(input, 0, 4).unwrap());
772 assert_eq!(lines_span[1], Span::new(input, 4, 8).unwrap());
773 assert_eq!(
774 lines_span
775 .iter()
776 .map(|span| span.as_str())
777 .collect::<Vec<_>>(),
778 lines
779 );
780 }
781
782 #[test]
783 fn get_input_of_span() {
784 let input = "abc\ndef\nghi";
785 let span = Span::new(input, 1, 7).unwrap();
786
787 assert_eq!(span.get_input(), input);
788 }
789
790 #[test]
791 fn merge_contiguous() {
792 let input = "abc\ndef\nghi";
793 let span1 = Span::new(input, 1, 7).unwrap();
794 let span2 = Span::new(input, 7, 11).unwrap();
795 let merged = merge_spans(&span1, &span2).unwrap();
796
797 assert_eq!(merged, Span::new(input, 1, 11).unwrap());
798 }
799
800 #[test]
801 fn merge_overlapping() {
802 let input = "abc\ndef\nghi";
803 let span1 = Span::new(input, 1, 7).unwrap();
804 let span2 = Span::new(input, 5, 11).unwrap();
805 let merged = merge_spans(&span1, &span2).unwrap();
806
807 assert_eq!(merged, Span::new(input, 1, 11).unwrap());
808 }
809
810 #[test]
811 fn merge_non_contiguous() {
812 let input = "abc\ndef\nghi";
813 let span1 = Span::new(input, 1, 7).unwrap();
814 let span2 = Span::new(input, 8, 11).unwrap();
815 let merged = merge_spans(&span1, &span2);
816
817 assert!(merged.is_none());
818 }
819}