Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merging Spans 鈾伙笍 #887

Merged
merged 3 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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());
}
}