-
-
Notifications
You must be signed in to change notification settings - Fork 250
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
Implement Span::sub_span
and Span::lines_span
.
#456
Closed
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
5f80e2e
Implement `Span::sub_span` and `Span::lines_span`.
tangmi 3450f7d
Fix subspan range bug
tangmi 5c6546e
Make lifetime restrictions less strict
tangmi 3dc681e
Fix case in LinesSpan iterator when the end of iteration is a partial…
tangmi 12de8dc
Expose LinesSpan
tangmi File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,32 @@ impl<'i> Span<'i> { | |
} | ||
} | ||
|
||
/// Attempts to create a new span based on a sub-range. | ||
/// | ||
/// TODO better docs | ||
pub fn sub_span(&self, range: impl std::ops::RangeBounds<usize>) -> Option<Span<'i>> { | ||
let start = match range.start_bound() { | ||
std::ops::Bound::Included(&offset) => offset, | ||
std::ops::Bound::Excluded(&offset) => offset + 1, | ||
std::ops::Bound::Unbounded => 0, | ||
}; | ||
let end = match range.end_bound() { | ||
std::ops::Bound::Included(&offset) => Some(offset + 1), | ||
std::ops::Bound::Excluded(&offset) => Some(offset), | ||
std::ops::Bound::Unbounded => None, | ||
}; | ||
let s = self.as_str(); | ||
if s.get(start..end.unwrap_or(s.len())).is_some() { | ||
Span::new( | ||
self.input, | ||
self.start + start, | ||
end.map(|n| self.start + n).unwrap_or(self.end), | ||
) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
/// Returns the `Span`'s start byte position as a `usize`. | ||
/// | ||
/// # Examples | ||
|
@@ -199,9 +225,19 @@ impl<'i> Span<'i> { | |
/// assert_eq!(span.lines().collect::<Vec<_>>(), vec!["b\n", "c"]); | ||
/// ``` | ||
#[inline] | ||
pub fn lines(&self) -> Lines { | ||
pub fn lines(&self) -> Lines<'i> { | ||
Lines { | ||
span: self, | ||
inner: self.lines_span(), | ||
} | ||
} | ||
|
||
/// Unlike `Span::lines`, will not emit the entire line for partially covered lines. Use `Span::start_pos().line_of()` if this behavior is desired. | ||
/// | ||
/// TODO better docs | ||
#[inline] | ||
pub fn lines_span(&self) -> LinesSpan<'i> { | ||
LinesSpan { | ||
span: self.clone(), | ||
pos: self.start, | ||
} | ||
} | ||
|
@@ -239,30 +275,82 @@ impl<'i> Hash for Span<'i> { | |
/// | ||
/// [`Span::lines()`]: struct.Span.html#method.lines | ||
pub struct Lines<'i> { | ||
span: &'i Span<'i>, | ||
pos: usize, | ||
inner: LinesSpan<'i>, | ||
} | ||
|
||
impl<'i> Iterator for Lines<'i> { | ||
type Item = &'i str; | ||
fn next(&mut self) -> Option<&'i str> { | ||
self.inner.next().map(|span| span.start_pos().line_of()) | ||
} | ||
} | ||
|
||
/// Like `Lines`, but returns `Span`s to preserve source mapping information. `Lines` simply calls this and `span.start_pos().line_of()`. | ||
/// | ||
/// TODO better docs | ||
pub struct LinesSpan<'i> { | ||
span: Span<'i>, | ||
pos: usize, | ||
} | ||
|
||
impl<'i> Iterator for LinesSpan<'i> { | ||
type Item = Span<'i>; | ||
fn next(&mut self) -> Option<Span<'i>> { | ||
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 = self.pos; | ||
self.pos = pos.find_line_end(); | ||
Some(line) | ||
Some(unsafe { | ||
Span::new_unchecked(self.span.input, line_start, self.pos.min(self.span.end)) | ||
}) | ||
Comment on lines
+308
to
+310
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Alextopher could this be done without |
||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn sub_span() { | ||
let input = "abcde"; | ||
let span = Span::new(input, 0, input.len()).unwrap(); | ||
assert_eq!(span.sub_span(..).unwrap().as_str(), "abcde"); | ||
assert_eq!(span.sub_span(..3).unwrap().as_str(), "abc"); | ||
assert_eq!(span.sub_span(..=3).unwrap().as_str(), "abcd"); | ||
assert_eq!(span.sub_span(1..).unwrap().as_str(), "bcde"); | ||
assert_eq!(span.sub_span(1..3).unwrap().as_str(), "bc"); | ||
assert_eq!(span.sub_span(1..=3).unwrap().as_str(), "bcd"); | ||
} | ||
|
||
#[test] | ||
fn sub_span_out_of_range() { | ||
let input = "abc"; | ||
let span = Span::new(input, 0, 1).unwrap(); | ||
assert_eq!(span.sub_span(..).unwrap().as_str(), "a"); | ||
|
||
// Even though `input` has this character, a sub span cannot be created. | ||
assert_eq!(span.sub_span(..2), None); | ||
} | ||
|
||
#[test] | ||
fn sub_span_preserve_input() { | ||
let input = "abc"; | ||
let span = Span::new(input, 1, input.len()).unwrap(); | ||
|
||
// The user can no longer access `span.input` to see the leading `a`. | ||
assert_eq!(span.as_str(), "bc"); | ||
|
||
// If the user wants to process a portion of `span`, they can do so preserving the original `span.input`. | ||
let sub_span = span.sub_span(1..2).unwrap(); | ||
assert_eq!(sub_span.as_str(), "c"); | ||
assert_eq!(sub_span.start_pos().line_of(), "abc"); | ||
} | ||
|
||
#[test] | ||
fn split() { | ||
let input = "a"; | ||
|
@@ -276,6 +364,30 @@ mod tests { | |
assert_eq!(span.split(), (start, end)); | ||
} | ||
|
||
#[test] | ||
fn lines_span_mid() { | ||
let input = "abc\ndef\nghi"; | ||
let span = Span::new(input, 1, 7).unwrap(); | ||
let lines: Vec<_> = span.lines_span().collect(); | ||
println!("{:?}", lines); | ||
assert_eq!(lines.len(), 2); | ||
assert_eq!(lines[0].as_str(), "bc\n".to_owned()); // `Span::lines_span` preserves the "partial" coverage of the span. | ||
assert_eq!(lines[1].as_str(), "def\n".to_owned()); | ||
} | ||
|
||
/// Assures that a `Span` that ends in a newline character behaves as if nothing else exists after it. | ||
#[test] | ||
fn lines_span_end_of_line() { | ||
let input = "abc\ndef"; | ||
let span = Span::new(input, 0, 4).unwrap(); | ||
let lines: Vec<_> = span.lines_span().collect(); | ||
println!("{:?}", lines); | ||
assert_eq!(lines.len(), 2); | ||
assert_eq!(lines[0].as_str(), "abc\n".to_owned()); | ||
assert_eq!(lines[1].as_str(), "".to_owned()); | ||
assert_eq!(lines[1].start_pos().line_of(), "def".to_owned()); | ||
} | ||
|
||
#[test] | ||
fn lines_mid() { | ||
let input = "abc\ndef\nghi"; | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Alextopher one more item to fix: the merge conflict seems to be about
core
vsstd
. pest now supportsno_std
, so theseops
may either need to be adjusted forno_std
or sub_span should be feature-guarded forstd