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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Span::lines_span() #682

Merged
merged 2 commits into from
Aug 4, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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 @@ -77,7 +77,7 @@ extern crate serde_json;
pub use crate::parser::Parser;
pub use crate::parser_state::{state, Atomicity, Lookahead, MatchDir, ParseResult, ParserState};
pub use crate::position::Position;
pub use crate::span::{Lines, Span};
pub use crate::span::{Lines, LinesSpan, Span};
pub use crate::token::Token;
use core::fmt::Debug;
use core::hash::Hash;
Expand Down
89 changes: 77 additions & 12 deletions pest/src/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl<'i> Span<'i> {
&self.input[self.start..self.end]
}

/// Iterates over all lines (partially) covered by this span.
/// Iterates over all lines (partially) covered by this span. Yielding a `&str` for each line.
///
/// # Examples
///
Expand All @@ -200,6 +200,30 @@ impl<'i> Span<'i> {
#[inline]
pub fn lines(&self) -> Lines {
Lines {
inner: self.lines_span(),
}
}

/// Iterates over all lines (partially) covered by this span. Yielding a `Span` for each line.
///
/// # Examples
///
/// ```
/// # use pest;
/// # use pest::Span;
/// # #[allow(non_camel_case_types)]
/// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
/// enum Rule {}
///
/// let input = "a\nb\nc";
/// let mut state: Box<pest::ParserState<Rule>> = pest::ParserState::new(input).skip(2).unwrap();
/// let start_pos = state.position().clone();
/// state = state.match_string("b\nc").unwrap();
/// let span = start_pos.span(&state.position().clone());
/// assert_eq!(span.lines_span().collect::<Vec<_>>(), vec![Span::new(input, 2, 4).unwrap(), Span::new(input, 4, 5).unwrap()]);
/// ```
pub fn lines_span(&self) -> LinesSpan {
LinesSpan {
span: self,
pos: self.start,
}
Expand Down Expand Up @@ -232,29 +256,47 @@ impl<'i> Hash for Span<'i> {
}
}

/// Line iterator for Spans, created by [`Span::lines()`].
/// Line iterator for Spans, created by [`Span::lines_span()`].
///
/// Iterates all lines that are at least partially covered by the span.
/// Iterates all lines that are at least _partially_ covered by the span. Yielding a `Span` for each.
///
/// [`Span::lines()`]: struct.Span.html#method.lines
pub struct Lines<'i> {
/// [`Span::lines_span()`]: struct.Span.html#method.lines_span
pub struct LinesSpan<'i> {
span: &'i Span<'i>,
pos: usize,
}

impl<'i> Iterator for Lines<'i> {
type Item = &'i str;
fn next(&mut self) -> Option<&'i str> {
impl<'i> Iterator for LinesSpan<'i> {
type Item = Span<'i>;
fn next(&mut self) -> Option<Self::Item> {
if self.pos > self.span.end {
return None;
}
let pos = position::Position::new(self.span.input, self.pos)?;
if pos.at_end() {
return None;
}
let line = pos.line_of();

let line_start = pos.find_line_start();
self.pos = pos.find_line_end();
Some(line)

Span::new(self.span.input, line_start, self.pos)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the previous implementation this unsafely used Span::new_unchecked. It should be safe because find_line_end should never extend pass input. We could save a few bounds check however it's not necessary.

}
}

/// Line iterator for Spans, created by [`Span::lines()`].
///
/// Iterates all lines that are at least _partially_ covered by the span. Yielding a `&str` for each.
///
/// [`Span::lines()`]: struct.Span.html#method.lines
pub struct Lines<'i> {
inner: LinesSpan<'i>,
}

impl<'i> Iterator for Lines<'i> {
type Item = &'i str;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|span| span.as_str())
}
}

Expand Down Expand Up @@ -292,10 +334,12 @@ mod tests {
let input = "abc\ndef\nghi";
let span = Span::new(input, 1, 7).unwrap();
let lines: Vec<_> = span.lines().collect();
//println!("{:?}", lines);
let lines_span: Vec<_> = span.lines_span().map(|span| span.as_str()).collect();

assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "abc\n".to_owned());
assert_eq!(lines[1], "def\n".to_owned());
assert_eq!(lines, lines_span) // Verify parity with lines_span()
}

#[test]
Expand All @@ -305,9 +349,30 @@ mod tests {
assert!(span.end_pos().at_end());
assert_eq!(span.end(), 11);
let lines: Vec<_> = span.lines().collect();
//println!("{:?}", lines);
let lines_span: Vec<_> = span.lines_span().map(|span| span.as_str()).collect();

assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "def\n".to_owned());
assert_eq!(lines[1], "ghi".to_owned());
assert_eq!(lines, lines_span) // Verify parity with lines_span()
}

#[test]
fn lines_span() {
let input = "abc\ndef\nghi";
let span = Span::new(input, 1, 7).unwrap();
let lines_span: Vec<_> = span.lines_span().collect();
let lines: Vec<_> = span.lines().collect();

assert_eq!(lines_span.len(), 2);
assert_eq!(lines_span[0], Span::new(input, 0, 4).unwrap());
assert_eq!(lines_span[1], Span::new(input, 4, 8).unwrap());
assert_eq!(
lines_span
.iter()
.map(|span| span.as_str())
.collect::<Vec<_>>(),
lines
);
}
}