Skip to content

Commit

Permalink
Merging Spans ♻️ (#887)
Browse files Browse the repository at this point in the history
* Added merge and get_input for Span.

* Corrected examples in doc-comments.
Reexporting merge_spans.

* Run cargo fmt and replaced std with core.
  • Loading branch information
0nyr committed Jul 16, 2023
1 parent 18cba90 commit 597e09b
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ pub use crate::parser_state::{
set_call_limit, state, Atomicity, Lookahead, MatchDir, ParseResult, ParserState,
};
pub use crate::position::Position;
pub use crate::span::{Lines, LinesSpan, Span};
pub use crate::span::{merge_spans, Lines, LinesSpan, Span};
pub use crate::token::Token;
use core::fmt::Debug;
use core::hash::Hash;
Expand Down
110 changes: 110 additions & 0 deletions pest/src/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,27 @@ impl<'i> Span<'i> {
&self.input[self.start..self.end]
}

/// Returns the input string of the `Span`.
///
/// This function returns the input string of the `Span` as a `&str`. This is the source string
/// from which the `Span` was created. The returned `&str` can be used to examine the contents of
/// the `Span` or to perform further processing on the string.
///
/// # Examples
///
/// ```
/// # use pest;
/// # use pest::Span;
///
/// // Example: Get input string from a span
/// let input = "abc\ndef\nghi";
/// let span = Span::new(input, 1, 7).unwrap();
/// assert_eq!(span.get_input(), input);
/// ```
pub fn get_input(&self) -> &'i str {
self.input
}

/// Iterates over all lines (partially) covered by this span. Yielding a `&str` for each line.
///
/// # Examples
Expand Down Expand Up @@ -288,6 +309,61 @@ impl<'i> Hash for Span<'i> {
}
}

/// Merges two spans into one.
///
/// This function merges two spans that are contiguous or overlapping into a single span
/// that covers the entire range of the two input spans. This is useful when you want to
/// aggregate information from multiple spans into a single entity.
///
/// The function checks if the input spans are overlapping or contiguous by comparing their
/// start and end positions. If they are, a new span is created with the minimum start position
/// and the maximum end position of the two input spans.
///
/// If the input spans are neither overlapping nor contiguous, the function returns None,
/// indicating that a merge operation was not possible.
///
/// # Examples
///
/// ```
/// # use pest;
/// # use pest::Span;
/// # use pest::merge_spans;
///
/// // Example 1: Contiguous spans
/// let input = "abc\ndef\nghi";
/// let span1 = Span::new(input, 1, 7).unwrap();
/// let span2 = Span::new(input, 7, 11).unwrap();
/// let merged = merge_spans(&span1, &span2).unwrap();
/// assert_eq!(merged, Span::new(input, 1, 11).unwrap());
///
/// // Example 2: Overlapping spans
/// let input = "abc\ndef\nghi";
/// let span1 = Span::new(input, 1, 7).unwrap();
/// let span2 = Span::new(input, 5, 11).unwrap();
/// let merged = merge_spans(&span1, &span2).unwrap();
/// assert_eq!(merged, Span::new(input, 1, 11).unwrap());
///
/// // Example 3: Non-contiguous spans
/// let input = "abc\ndef\nghi";
/// let span1 = Span::new(input, 1, 7).unwrap();
/// let span2 = Span::new(input, 8, 11).unwrap();
/// let merged = merge_spans(&span1, &span2);
/// assert!(merged.is_none());
/// ```
pub fn merge_spans<'i>(a: &Span<'i>, b: &Span<'i>) -> Option<Span<'i>> {
if a.end() >= b.start() && a.start() <= b.end() {
// The spans overlap or are contiguous, so they can be merged.
Span::new(
a.get_input(),
core::cmp::min(a.start(), b.start()),
core::cmp::max(a.end(), b.end()),
)
} else {
// The spans don't overlap and aren't contiguous, so they can't be merged.
None
}
}

/// Line iterator for Spans, created by [`Span::lines_span()`].
///
/// Iterates all lines that are at least _partially_ covered by the span. Yielding a `Span` for each.
Expand Down Expand Up @@ -447,4 +523,38 @@ mod tests {
lines
);
}

#[test]
fn get_input() {
let input = "abc\ndef\nghi";
let span = Span::new(input, 1, 7).unwrap();
assert_eq!(span.get_input(), input);
}

#[test]
fn merge_contiguous() {
let input = "abc\ndef\nghi";
let span1 = Span::new(input, 1, 7).unwrap();
let span2 = Span::new(input, 7, 11).unwrap();
let merged = merge_spans(&span1, &span2).unwrap();
assert_eq!(merged, Span::new(input, 1, 11).unwrap());
}

#[test]
fn merge_overlapping() {
let input = "abc\ndef\nghi";
let span1 = Span::new(input, 1, 7).unwrap();
let span2 = Span::new(input, 5, 11).unwrap();
let merged = merge_spans(&span1, &span2).unwrap();
assert_eq!(merged, Span::new(input, 1, 11).unwrap());
}

#[test]
fn merge_non_contiguous() {
let input = "abc\ndef\nghi";
let span1 = Span::new(input, 1, 7).unwrap();
let span2 = Span::new(input, 8, 11).unwrap();
let merged = merge_spans(&span1, &span2);
assert!(merged.is_none());
}
}

0 comments on commit 597e09b

Please sign in to comment.