use std::collections::HashMap;
use std::mem::take;
use std::sync::Arc;
use crate::cell_stream::{FramedCells, frame_recovering};
use crate::lexer::lex_source_recovering;
use crate::nodes::visit_mut::{self, VisitMut};
use crate::nodes::{Cell, SourceFile};
use crate::span::Offset;
use crate::{
CellTokenStream, CommentTrivia, Delimiter, DelimiterValue, LeadingTrivia, LexError, Source,
Spanned, Terminal, TokenStream, TokenTree, TrailingTrivia, Trivia,
};
type Bounds = (Offset, Offset);
struct GroupTrivia {
opening: TrailingTrivia,
closing: TrailingTrivia,
}
#[derive(Default)]
pub(crate) struct TriviaTransfer {
terminals: HashMap<Bounds, TrailingTrivia>,
groups: HashMap<Bounds, GroupTrivia>,
cells: HashMap<Offset, LeadingTrivia>,
closing: Vec<Trivia>,
}
impl TriviaTransfer {
pub(crate) fn from_source(source: &Arc<Source>) -> Result<Self, LexError> {
let FramedCells { cells, incomplete } =
frame_recovering(lex_source_recovering(source)?.value);
Ok(Self::from_cells(cells.into_iter().chain(incomplete)))
}
fn from_cells(cells: impl IntoIterator<Item = CellTokenStream>) -> Self {
let mut transfer = Self::default();
let mut detached = Vec::new();
for cell in cells {
let mut tokens = cell.into_parser_token_stream();
let Some(start) = tokens.iter().next().and_then(|first| first.span().start()) else {
detached.extend(CommentTrivia::of(&tokens).into_trivia());
continue;
};
detached.extend(tokens.take_prefix_trivia().into_vec());
transfer
.cells
.insert(start, LeadingTrivia::new(take(&mut detached)));
transfer.collect_stream(&tokens);
}
transfer.closing = detached;
transfer
}
pub(crate) fn apply(mut self, source_file: &mut SourceFile) {
self.visit_source_file_mut(source_file);
source_file.set_closing_comments(take(&mut self.closing));
}
fn collect_stream(&mut self, tokens: &TokenStream) {
for token in tokens {
let Some(bounds) = token.span().bounds() else {
continue;
};
if let TokenTree::Group(group) = token {
self.collect_stream(group.stream());
let Some(opening) = group.delimiter().spans.open().bounds() else {
continue;
};
self.groups.insert(
opening,
GroupTrivia {
opening: TrailingTrivia::new(group.stream().prefix_trivia().to_vec()),
closing: token.trailing_trivia().clone(),
},
);
} else if !token.trailing_trivia().is_empty() {
self.terminals
.insert(bounds, token.trailing_trivia().clone());
}
}
}
}
impl VisitMut for TriviaTransfer {
fn visit_cell_mut(&mut self, cell: &mut Cell) {
if let Some(leading) = cell
.try_span()
.and_then(|span| span.start())
.and_then(|start| self.cells.remove(&start))
{
cell.leading_trivia = leading;
}
visit_mut::visit_cell_mut(self, cell);
}
fn visit_terminal_mut(&mut self, terminal: &mut dyn Terminal) {
if let Some(trailing) = terminal
.try_span()
.and_then(|span| span.bounds())
.and_then(|bounds| self.terminals.remove(&bounds))
{
*terminal.trailing_trivia_mut() = trailing;
}
}
fn visit_delimiter_mut(&mut self, delimiter: &mut Delimiter) {
if let Some(GroupTrivia { opening, closing }) = delimiter
.spans
.open()
.bounds()
.and_then(|bounds| self.groups.remove(&bounds))
{
*delimiter.opening_trivia_mut() = opening;
*delimiter.closing_trivia_mut() = closing;
}
}
}