use crate::ast::Span;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum TriviaKind {
LineComment,
BlockComment,
Whitespace,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct TriviaRange {
span: Span,
kind: TriviaKind,
}
impl TriviaRange {
pub fn new(kind: TriviaKind, span: Span) -> Self {
Self { span, kind }
}
pub fn span(&self) -> Span {
self.span
}
pub fn kind(&self) -> TriviaKind {
self.kind
}
}
#[derive(Clone, Default, Debug)]
pub struct TriviaIndex {
ranges: Vec<TriviaRange>,
}
impl TriviaIndex {
pub(crate) fn new(ranges: Vec<TriviaRange>) -> Self {
debug_assert!(
ranges.windows(2).all(|pair| {
let (prev, next) = (pair[0].span, pair[1].span);
prev.start() <= next.start() && prev.end() <= next.start()
}),
"trivia must be recorded sorted and non-overlapping",
);
Self { ranges }
}
pub fn all(&self) -> &[TriviaRange] {
&self.ranges
}
pub fn len(&self) -> usize {
self.ranges.len()
}
pub fn is_empty(&self) -> bool {
self.ranges.is_empty()
}
pub fn in_span(&self, span: Span) -> &[TriviaRange] {
if span.is_synthetic() {
return &[];
}
let lo = self
.ranges
.partition_point(|r| r.span.start() < span.start());
let hi = self.ranges.partition_point(|r| r.span.end() <= span.end());
if lo >= hi {
return &[];
}
&self.ranges[lo..hi]
}
pub fn before(&self, offset: u32) -> &[TriviaRange] {
let end = self.ranges.partition_point(|r| r.span.end() <= offset);
let mut start = end;
let mut boundary = offset;
while start > 0 {
let span = self.ranges[start - 1].span;
if span.end() != boundary {
break;
}
boundary = span.start();
start -= 1;
}
&self.ranges[start..end]
}
}
pub(crate) trait TriviaSink {
const RECORDING: bool;
fn record(&mut self, range: TriviaRange);
}
pub(crate) struct NoTrivia;
impl TriviaSink for NoTrivia {
const RECORDING: bool = false;
#[inline(always)]
fn record(&mut self, _range: TriviaRange) {}
}
impl TriviaSink for Vec<TriviaRange> {
const RECORDING: bool = true;
#[inline]
fn record(&mut self, range: TriviaRange) {
self.push(range);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn index(runs: &[(TriviaKind, u32, u32)]) -> TriviaIndex {
TriviaIndex::new(
runs.iter()
.map(|&(kind, start, end)| TriviaRange::new(kind, Span::new(start, end)))
.collect(),
)
}
#[test]
fn empty_index_allocates_nothing_and_queries_empty() {
let empty = TriviaIndex::default();
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
assert_eq!(empty.all(), &[]);
assert!(empty.in_span(Span::new(0, 100)).is_empty());
assert!(empty.before(0).is_empty());
}
#[test]
fn in_span_returns_only_fully_contained_runs() {
use TriviaKind::{BlockComment, LineComment, Whitespace};
let idx = index(&[
(Whitespace, 1, 2), (BlockComment, 2, 7), (Whitespace, 7, 8), (Whitespace, 9, 10), (LineComment, 10, 17), (Whitespace, 17, 19), ]);
let inner = idx.in_span(Span::new(2, 10));
assert_eq!(
inner.iter().map(TriviaRange::kind).collect::<Vec<_>>(),
[BlockComment, Whitespace, Whitespace],
);
assert!(idx.in_span(Span::new(3, 7)).is_empty());
assert_eq!(idx.in_span(Span::new(0, 19)).len(), 6);
assert!(idx.in_span(Span::SYNTHETIC).is_empty());
}
#[test]
fn before_collects_the_contiguous_leading_chain() {
use TriviaKind::{BlockComment, LineComment, Whitespace};
let idx = index(&[
(Whitespace, 5, 6), (BlockComment, 6, 11), (Whitespace, 11, 12), (LineComment, 20, 27), ]);
let leading = idx.before(12);
assert_eq!(
leading.iter().map(TriviaRange::kind).collect::<Vec<_>>(),
[Whitespace, BlockComment, Whitespace],
);
assert!(idx.before(20).is_empty());
assert_eq!(
idx.before(27),
&[TriviaRange::new(LineComment, Span::new(20, 27))]
);
assert!(idx.before(5).is_empty());
}
}