#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::{
ops::{Bound, Deref, RangeBounds},
slice,
};
use crate::tokenizer::{Location, Span};
#[derive(Default, Debug, Clone)]
pub struct Comments(Vec<CommentWithSpan>);
impl Comments {
pub(crate) fn offer(&mut self, comment: CommentWithSpan) {
if self
.0
.last()
.map(|last| last.span < comment.span)
.unwrap_or(true)
{
self.0.push(comment);
}
}
pub fn find<R: RangeBounds<Location>>(&self, range: R) -> Iter<'_> {
let (start, end) = (
self.start_index(range.start_bound()),
self.end_index(range.end_bound()),
);
debug_assert!((0..=self.0.len()).contains(&start));
debug_assert!((0..=self.0.len()).contains(&end));
Iter(if start <= end {
self.0[start..end].iter()
} else {
self.0[0..0].iter()
})
}
fn start_index(&self, location: Bound<&Location>) -> usize {
match location {
Bound::Included(location) => {
match self.0.binary_search_by(|c| c.span.start.cmp(location)) {
Ok(i) => i,
Err(i) => i,
}
}
Bound::Excluded(location) => {
match self.0.binary_search_by(|c| c.span.start.cmp(location)) {
Ok(i) => i + 1,
Err(i) => i,
}
}
Bound::Unbounded => 0,
}
}
fn end_index(&self, location: Bound<&Location>) -> usize {
match location {
Bound::Included(location) => {
match self.0.binary_search_by(|c| c.span.start.cmp(location)) {
Ok(i) => i + 1,
Err(i) => i,
}
}
Bound::Excluded(location) => {
match self.0.binary_search_by(|c| c.span.start.cmp(location)) {
Ok(i) => i,
Err(i) => i,
}
}
Bound::Unbounded => self.0.len(),
}
}
}
impl From<Comments> for Vec<CommentWithSpan> {
fn from(comments: Comments) -> Self {
comments.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CommentWithSpan {
pub comment: Comment,
pub span: Span,
}
impl Deref for CommentWithSpan {
type Target = Comment;
fn deref(&self) -> &Self::Target {
&self.comment
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Comment {
SingleLine {
content: String,
prefix: String,
},
MultiLine(String),
}
impl Comment {
pub fn as_str(&self) -> &str {
match self {
Comment::SingleLine { content, prefix: _ } => content.as_str(),
Comment::MultiLine(content) => content.as_str(),
}
}
}
impl Deref for Comment {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
pub struct Iter<'a>(slice::Iter<'a, CommentWithSpan>);
impl<'a> Iterator for Iter<'a> {
type Item = &'a CommentWithSpan;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find() {
let comments = {
let mut c = Comments(Vec::new());
c.offer(CommentWithSpan {
comment: Comment::SingleLine {
content: " abc".into(),
prefix: "--".into(),
},
span: Span::new((1, 1).into(), (1, 7).into()),
});
c.offer(CommentWithSpan {
comment: Comment::MultiLine(" hello ".into()),
span: Span::new((2, 3).into(), (2, 14).into()),
});
c.offer(CommentWithSpan {
comment: Comment::SingleLine {
content: ", world".into(),
prefix: "--".into(),
},
span: Span::new((2, 14).into(), (2, 21).into()),
});
c.offer(CommentWithSpan {
comment: Comment::MultiLine(" def\n ghi\n jkl\n".into()),
span: Span::new((3, 3).into(), (7, 1).into()),
});
c
};
fn find<R: RangeBounds<Location>>(comments: &Comments, range: R) -> Vec<&str> {
comments.find(range).map(|c| c.as_str()).collect::<Vec<_>>()
}
assert_eq!(find(&comments, ..Location::new(0, 0)), Vec::<&str>::new());
assert_eq!(find(&comments, ..Location::new(2, 1)), vec![" abc"]);
assert_eq!(find(&comments, ..Location::new(2, 3)), vec![" abc"]);
assert_eq!(
find(&comments, ..=Location::new(2, 3)),
vec![" abc", " hello "]
);
assert_eq!(
find(&comments, ..=Location::new(2, 3)),
vec![" abc", " hello "]
);
assert_eq!(
find(&comments, ..Location::new(2, 15)),
vec![" abc", " hello ", ", world"]
);
assert_eq!(
find(&comments, Location::new(1000, 1000)..),
Vec::<&str>::new()
);
assert_eq!(
find(&comments, Location::new(2, 14)..),
vec![", world", " def\n ghi\n jkl\n"]
);
assert_eq!(
find(&comments, Location::new(2, 15)..),
vec![" def\n ghi\n jkl\n"]
);
assert_eq!(
find(&comments, Location::new(0, 0)..),
vec![" abc", " hello ", ", world", " def\n ghi\n jkl\n"]
);
assert_eq!(
find(&comments, Location::new(1, 1)..),
vec![" abc", " hello ", ", world", " def\n ghi\n jkl\n"]
);
assert_eq!(
find(&comments, Location::new(2, 1)..Location::new(1, 1)),
Vec::<&str>::new()
);
assert_eq!(
find(&comments, Location::new(1, 1)..Location::new(2, 3)),
vec![" abc"]
);
assert_eq!(
find(&comments, Location::new(1, 1)..=Location::new(2, 3)),
vec![" abc", " hello "]
);
assert_eq!(
find(&comments, Location::new(1, 1)..=Location::new(2, 10)),
vec![" abc", " hello "]
);
assert_eq!(
find(&comments, Location::new(1, 1)..=Location::new(2, 14)),
vec![" abc", " hello ", ", world"]
);
assert_eq!(
find(&comments, Location::new(1, 1)..Location::new(2, 15)),
vec![" abc", " hello ", ", world"]
);
assert_eq!(
find(&comments, ..),
vec![" abc", " hello ", ", world", " def\n ghi\n jkl\n"]
);
}
}