Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/tt/src/lib.rs')
| -rw-r--r-- | crates/tt/src/lib.rs | 616 |
1 files changed, 486 insertions, 130 deletions
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs index 8d915d0a51..7705ba876e 100644 --- a/crates/tt/src/lib.rs +++ b/crates/tt/src/lib.rs @@ -1,6 +1,7 @@ //! `tt` crate defines a `TokenTree` data structure: this is the interface (both -//! input and output) of macros. It closely mirrors `proc_macro` crate's -//! `TokenTree`. +//! input and output) of macros. +//! +//! The `TokenTree` is semantically a tree, but for performance reasons it is stored as a flat structure. #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] @@ -14,7 +15,9 @@ pub mod iter; use std::fmt; +use buffer::Cursor; use intern::Symbol; +use iter::{TtElement, TtIter}; use stdx::{impl_from, itertools::Itertools as _}; pub use text_size::{TextRange, TextSize}; @@ -75,23 +78,6 @@ pub enum TokenTree<S = u32> { } impl_from!(Leaf<S>, Subtree<S> for TokenTree); impl<S: Copy> TokenTree<S> { - pub fn empty(span: S) -> Self { - Self::Subtree(Subtree { - delimiter: Delimiter::invisible_spanned(span), - token_trees: Box::new([]), - }) - } - - pub fn subtree_or_wrap(self, span: DelimSpan<S>) -> Subtree<S> { - match self { - TokenTree::Leaf(_) => Subtree { - delimiter: Delimiter::invisible_delim_spanned(span), - token_trees: Box::new([self]), - }, - TokenTree::Subtree(s) => s, - } - } - pub fn first_span(&self) -> S { match self { TokenTree::Leaf(l) => *l.span(), @@ -118,38 +104,422 @@ impl<S> Leaf<S> { } impl_from!(Literal<S>, Punct<S>, Ident<S> for Leaf); -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Subtree<S> { pub delimiter: Delimiter<S>, - pub token_trees: Box<[TokenTree<S>]>, + /// Number of following token trees that belong to this subtree, excluding this subtree. + pub len: u32, } -impl<S: Copy> Subtree<S> { +impl<S> Subtree<S> { + pub fn usize_len(&self) -> usize { + self.len as usize + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct TopSubtree<S>(pub Box<[TokenTree<S>]>); + +impl<S: Copy> TopSubtree<S> { pub fn empty(span: DelimSpan<S>) -> Self { - Subtree { delimiter: Delimiter::invisible_delim_spanned(span), token_trees: Box::new([]) } + Self(Box::new([TokenTree::Subtree(Subtree { + delimiter: Delimiter::invisible_delim_spanned(span), + len: 0, + })])) + } + + pub fn invisible_from_leaves<const N: usize>(delim_span: S, leaves: [Leaf<S>; N]) -> Self { + let mut builder = TopSubtreeBuilder::new(Delimiter::invisible_spanned(delim_span)); + builder.extend(leaves); + builder.build() + } + + pub fn from_token_trees(delimiter: Delimiter<S>, token_trees: TokenTreesView<'_, S>) -> Self { + let mut builder = TopSubtreeBuilder::new(delimiter); + builder.extend_with_tt(token_trees); + builder.build() + } + + pub fn from_subtree(subtree: SubtreeView<'_, S>) -> Self { + Self(subtree.0.into()) + } + + pub fn view(&self) -> SubtreeView<'_, S> { + SubtreeView::new(&self.0) + } + + pub fn iter(&self) -> TtIter<'_, S> { + self.view().iter() } - /// This is slow, and should be avoided, as it will always reallocate! - pub fn push(&mut self, subtree: TokenTree<S>) { - let mut mutable_trees = std::mem::take(&mut self.token_trees).into_vec(); + pub fn top_subtree(&self) -> &Subtree<S> { + self.view().top_subtree() + } - // Reserve exactly space for one element, to avoid `into_boxed_slice` having to reallocate again. - mutable_trees.reserve_exact(1); - mutable_trees.push(subtree); + pub fn top_subtree_delimiter_mut(&mut self) -> &mut Delimiter<S> { + let TokenTree::Subtree(subtree) = &mut self.0[0] else { + unreachable!("the first token tree is always the top subtree"); + }; + &mut subtree.delimiter + } - self.token_trees = mutable_trees.into_boxed_slice(); + pub fn token_trees(&self) -> TokenTreesView<'_, S> { + self.view().token_trees() } } -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct SubtreeBuilder<S> { - pub delimiter: Delimiter<S>, - pub token_trees: Vec<TokenTree<S>>, +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TopSubtreeBuilder<S> { + unclosed_subtree_indices: Vec<usize>, + token_trees: Vec<TokenTree<S>>, + last_closed_subtree: Option<usize>, +} + +impl<S: Copy> TopSubtreeBuilder<S> { + pub fn new(top_delimiter: Delimiter<S>) -> Self { + let mut result = Self { + unclosed_subtree_indices: Vec::new(), + token_trees: Vec::new(), + last_closed_subtree: None, + }; + let top_subtree = TokenTree::Subtree(Subtree { delimiter: top_delimiter, len: 0 }); + result.token_trees.push(top_subtree); + result + } + + pub fn open(&mut self, delimiter_kind: DelimiterKind, open_span: S) { + self.unclosed_subtree_indices.push(self.token_trees.len()); + self.token_trees.push(TokenTree::Subtree(Subtree { + delimiter: Delimiter { + open: open_span, + close: open_span, // Will be overwritten on close. + kind: delimiter_kind, + }, + len: 0, + })); + } + + pub fn close(&mut self, close_span: S) { + let last_unclosed_index = self + .unclosed_subtree_indices + .pop() + .expect("attempt to close a `tt::Subtree` when none is open"); + let subtree_len = (self.token_trees.len() - last_unclosed_index - 1) as u32; + let TokenTree::Subtree(subtree) = &mut self.token_trees[last_unclosed_index] else { + unreachable!("unclosed token tree is always a subtree"); + }; + subtree.len = subtree_len; + subtree.delimiter.close = close_span; + self.last_closed_subtree = Some(last_unclosed_index); + } + + /// You cannot call this consecutively, it will only work once after close. + pub fn remove_last_subtree_if_invisible(&mut self) { + let Some(last_subtree_idx) = self.last_closed_subtree else { return }; + if let TokenTree::Subtree(Subtree { + delimiter: Delimiter { kind: DelimiterKind::Invisible, .. }, + .. + }) = self.token_trees[last_subtree_idx] + { + self.token_trees.remove(last_subtree_idx); + self.last_closed_subtree = None; + } + } + + pub fn push(&mut self, leaf: Leaf<S>) { + self.token_trees.push(TokenTree::Leaf(leaf)); + } + + pub fn extend(&mut self, leaves: impl IntoIterator<Item = Leaf<S>>) { + self.token_trees.extend(leaves.into_iter().map(TokenTree::Leaf)); + } + + /// This does not check the token trees are valid, beware! + pub fn extend_tt_dangerous(&mut self, tt: impl IntoIterator<Item = TokenTree<S>>) { + self.token_trees.extend(tt); + } + + pub fn extend_with_tt(&mut self, tt: TokenTreesView<'_, S>) { + self.token_trees.extend(tt.0.iter().cloned()); + } + + pub fn expected_delimiter(&self) -> Option<&Delimiter<S>> { + self.unclosed_subtree_indices.last().map(|&subtree_idx| { + let TokenTree::Subtree(subtree) = &self.token_trees[subtree_idx] else { + unreachable!("unclosed token tree is always a subtree") + }; + &subtree.delimiter + }) + } + + /// Converts unclosed subtree to a punct of their open delimiter. + // FIXME: This is incorrect to do, delimiters can never be puncts. See #18244. + pub fn flatten_unclosed_subtrees(&mut self) { + for &subtree_idx in &self.unclosed_subtree_indices { + let TokenTree::Subtree(subtree) = &self.token_trees[subtree_idx] else { + unreachable!("unclosed token tree is always a subtree") + }; + let char = match subtree.delimiter.kind { + DelimiterKind::Parenthesis => '(', + DelimiterKind::Brace => '{', + DelimiterKind::Bracket => '[', + DelimiterKind::Invisible => '$', + }; + self.token_trees[subtree_idx] = TokenTree::Leaf(Leaf::Punct(Punct { + char, + spacing: Spacing::Alone, + span: subtree.delimiter.open, + })); + } + self.unclosed_subtree_indices.clear(); + } + + /// Builds, and remove the top subtree if it has only one subtree child. + pub fn build_skip_top_subtree(mut self) -> TopSubtree<S> { + let top_tts = TokenTreesView::new(&self.token_trees[1..]); + match top_tts.try_into_subtree() { + Some(_) => { + assert!( + self.unclosed_subtree_indices.is_empty(), + "attempt to build an unbalanced `TopSubtreeBuilder`" + ); + TopSubtree(self.token_trees.drain(1..).collect()) + } + None => self.build(), + } + } + + pub fn build(mut self) -> TopSubtree<S> { + assert!( + self.unclosed_subtree_indices.is_empty(), + "attempt to build an unbalanced `TopSubtreeBuilder`" + ); + let total_len = self.token_trees.len() as u32; + let TokenTree::Subtree(top_subtree) = &mut self.token_trees[0] else { + unreachable!("first token tree is always a subtree"); + }; + top_subtree.len = total_len - 1; + TopSubtree(self.token_trees.into_boxed_slice()) + } + + pub fn restore_point(&self) -> SubtreeBuilderRestorePoint { + SubtreeBuilderRestorePoint { + unclosed_subtree_indices_len: self.unclosed_subtree_indices.len(), + token_trees_len: self.token_trees.len(), + last_closed_subtree: self.last_closed_subtree, + } + } + + pub fn restore(&mut self, restore_point: SubtreeBuilderRestorePoint) { + self.unclosed_subtree_indices.truncate(restore_point.unclosed_subtree_indices_len); + self.token_trees.truncate(restore_point.token_trees_len); + self.last_closed_subtree = restore_point.last_closed_subtree; + } +} + +#[derive(Clone, Copy)] +pub struct SubtreeBuilderRestorePoint { + unclosed_subtree_indices_len: usize, + token_trees_len: usize, + last_closed_subtree: Option<usize>, +} + +#[derive(Clone, Copy)] +pub struct TokenTreesView<'a, S>(&'a [TokenTree<S>]); + +impl<'a, S: Copy> TokenTreesView<'a, S> { + pub fn new(tts: &'a [TokenTree<S>]) -> Self { + if cfg!(debug_assertions) { + tts.iter().enumerate().for_each(|(idx, tt)| { + if let TokenTree::Subtree(tt) = &tt { + // `<` and not `<=` because `Subtree.len` does not include the subtree node itself. + debug_assert!( + idx + tt.usize_len() < tts.len(), + "`TokenTreeView::new()` was given a cut-in-half list" + ); + } + }); + } + Self(tts) + } + + pub fn iter(&self) -> TtIter<'a, S> { + TtIter::new(self.0) + } + + pub fn cursor(&self) -> Cursor<'a, S> { + Cursor::new(self.0) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn try_into_subtree(self) -> Option<SubtreeView<'a, S>> { + if let Some(TokenTree::Subtree(subtree)) = self.0.first() { + if subtree.usize_len() == (self.0.len() - 1) { + return Some(SubtreeView::new(self.0)); + } + } + None + } + + pub fn strip_invisible(self) -> TokenTreesView<'a, S> { + self.try_into_subtree().map(|subtree| subtree.strip_invisible()).unwrap_or(self) + } + + /// This returns a **flat** structure of tokens (subtrees will be represented by a single node + /// preceding their children), so it isn't suited for most use cases, only for matching leaves + /// at the beginning/end with no subtrees before them. If you need a structured pass, use [`TtIter`]. + pub fn flat_tokens(&self) -> &'a [TokenTree<S>] { + self.0 + } + + pub fn split( + self, + mut split_fn: impl FnMut(TtElement<'a, S>) -> bool, + ) -> impl Iterator<Item = TokenTreesView<'a, S>> { + let mut subtree_iter = self.iter(); + let mut need_to_yield_even_if_empty = true; + let result = std::iter::from_fn(move || { + if subtree_iter.is_empty() && !need_to_yield_even_if_empty { + return None; + }; + + need_to_yield_even_if_empty = false; + let savepoint = subtree_iter.savepoint(); + let mut result = subtree_iter.from_savepoint(savepoint); + while let Some(tt) = subtree_iter.next() { + if split_fn(tt) { + need_to_yield_even_if_empty = true; + break; + } + result = subtree_iter.from_savepoint(savepoint); + } + Some(result) + }); + result + } } -impl<S> SubtreeBuilder<S> { - pub fn build(self) -> Subtree<S> { - Subtree { delimiter: self.delimiter, token_trees: self.token_trees.into_boxed_slice() } +impl<S: fmt::Debug + Copy> fmt::Debug for TokenTreesView<'_, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut iter = self.iter(); + while let Some(tt) = iter.next() { + print_debug_token(f, 0, tt)?; + if !iter.is_empty() { + writeln!(f)?; + } + } + Ok(()) + } +} + +impl<S: Copy> fmt::Display for TokenTreesView<'_, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + return token_trees_display(f, self.iter()); + + fn subtree_display<S>( + subtree: &Subtree<S>, + f: &mut fmt::Formatter<'_>, + iter: TtIter<'_, S>, + ) -> fmt::Result { + let (l, r) = match subtree.delimiter.kind { + DelimiterKind::Parenthesis => ("(", ")"), + DelimiterKind::Brace => ("{", "}"), + DelimiterKind::Bracket => ("[", "]"), + DelimiterKind::Invisible => ("", ""), + }; + f.write_str(l)?; + token_trees_display(f, iter)?; + f.write_str(r)?; + Ok(()) + } + + fn token_trees_display<S>(f: &mut fmt::Formatter<'_>, iter: TtIter<'_, S>) -> fmt::Result { + let mut needs_space = false; + for child in iter { + if needs_space { + f.write_str(" ")?; + } + needs_space = true; + + match child { + TtElement::Leaf(Leaf::Punct(p)) => { + needs_space = p.spacing == Spacing::Alone; + fmt::Display::fmt(p, f)?; + } + TtElement::Leaf(leaf) => fmt::Display::fmt(leaf, f)?, + TtElement::Subtree(subtree, subtree_iter) => { + subtree_display(subtree, f, subtree_iter)? + } + } + } + Ok(()) + } + } +} + +#[derive(Clone, Copy)] +// Invariant: always starts with `Subtree` that covers the entire thing. +pub struct SubtreeView<'a, S>(&'a [TokenTree<S>]); + +impl<'a, S: Copy> SubtreeView<'a, S> { + pub fn new(tts: &'a [TokenTree<S>]) -> Self { + if cfg!(debug_assertions) { + let TokenTree::Subtree(subtree) = &tts[0] else { + panic!("first token tree must be a subtree in `SubtreeView`"); + }; + assert_eq!( + subtree.usize_len(), + tts.len() - 1, + "subtree must cover the entire `SubtreeView`" + ); + } + Self(tts) + } + + pub fn as_token_trees(self) -> TokenTreesView<'a, S> { + TokenTreesView::new(self.0) + } + + pub fn iter(&self) -> TtIter<'a, S> { + TtIter::new(&self.0[1..]) + } + + pub fn top_subtree(&self) -> &'a Subtree<S> { + let TokenTree::Subtree(subtree) = &self.0[0] else { + unreachable!("the first token tree is always the top subtree"); + }; + subtree + } + + pub fn strip_invisible(&self) -> TokenTreesView<'a, S> { + if self.top_subtree().delimiter.kind == DelimiterKind::Invisible { + TokenTreesView::new(&self.0[1..]) + } else { + TokenTreesView::new(self.0) + } + } + + pub fn token_trees(&self) -> TokenTreesView<'a, S> { + TokenTreesView::new(&self.0[1..]) + } +} + +impl<S: fmt::Debug + Copy> fmt::Debug for SubtreeView<'_, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&TokenTreesView(self.0), f) + } +} + +impl<S: Copy> fmt::Display for SubtreeView<'_, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&TokenTreesView(self.0), f) } } @@ -348,6 +718,7 @@ fn print_debug_subtree<S: fmt::Debug>( f: &mut fmt::Formatter<'_>, subtree: &Subtree<S>, level: usize, + iter: TtIter<'_, S>, ) -> fmt::Result { let align = " ".repeat(level); @@ -363,14 +734,9 @@ fn print_debug_subtree<S: fmt::Debug>( fmt::Debug::fmt(&open, f)?; write!(f, " ")?; fmt::Debug::fmt(&close, f)?; - if !subtree.token_trees.is_empty() { + for child in iter { writeln!(f)?; - for (idx, child) in subtree.token_trees.iter().enumerate() { - print_debug_token(f, child, level + 1)?; - if idx != subtree.token_trees.len() - 1 { - writeln!(f)?; - } - } + print_debug_token(f, level + 1, child)?; } Ok(()) @@ -378,13 +744,13 @@ fn print_debug_subtree<S: fmt::Debug>( fn print_debug_token<S: fmt::Debug>( f: &mut fmt::Formatter<'_>, - tkn: &TokenTree<S>, level: usize, + tt: TtElement<'_, S>, ) -> fmt::Result { let align = " ".repeat(level); - match tkn { - TokenTree::Leaf(leaf) => match leaf { + match tt { + TtElement::Leaf(leaf) => match leaf { Leaf::Literal(lit) => { write!( f, @@ -417,54 +783,23 @@ fn print_debug_token<S: fmt::Debug>( )?; } }, - TokenTree::Subtree(subtree) => { - print_debug_subtree(f, subtree, level)?; + TtElement::Subtree(subtree, subtree_iter) => { + print_debug_subtree(f, subtree, level, subtree_iter)?; } } Ok(()) } -impl<S: fmt::Debug> fmt::Debug for Subtree<S> { +impl<S: fmt::Debug + Copy> fmt::Debug for TopSubtree<S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - print_debug_subtree(f, self, 0) + fmt::Debug::fmt(&self.view(), f) } } -impl<S> fmt::Display for TokenTree<S> { +impl<S: fmt::Display + Copy> fmt::Display for TopSubtree<S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TokenTree::Leaf(it) => fmt::Display::fmt(it, f), - TokenTree::Subtree(it) => fmt::Display::fmt(it, f), - } - } -} - -impl<S> fmt::Display for Subtree<S> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let (l, r) = match self.delimiter.kind { - DelimiterKind::Parenthesis => ("(", ")"), - DelimiterKind::Brace => ("{", "}"), - DelimiterKind::Bracket => ("[", "]"), - DelimiterKind::Invisible => ("", ""), - }; - f.write_str(l)?; - let mut needs_space = false; - for tt in self.token_trees.iter() { - if needs_space { - f.write_str(" ")?; - } - needs_space = true; - match tt { - TokenTree::Leaf(Leaf::Punct(p)) => { - needs_space = p.spacing == Spacing::Alone; - fmt::Display::fmt(p, f)?; - } - tt => fmt::Display::fmt(tt, f)?, - } - } - f.write_str(r)?; - Ok(()) + fmt::Display::fmt(&self.view(), f) } } @@ -538,34 +873,45 @@ impl<S> fmt::Display for Punct<S> { impl<S> Subtree<S> { /// Count the number of tokens recursively pub fn count(&self) -> usize { - let children_count = self - .token_trees - .iter() - .map(|c| match c { - TokenTree::Subtree(c) => c.count(), - TokenTree::Leaf(_) => 0, - }) - .sum::<usize>(); - - self.token_trees.len() + children_count + self.usize_len() } } -impl<S> Subtree<S> { +impl<S> TopSubtree<S> { /// A simple line string used for debugging - pub fn as_debug_string(&self) -> String { - let delim = match self.delimiter.kind { - DelimiterKind::Brace => ("{", "}"), - DelimiterKind::Bracket => ("[", "]"), - DelimiterKind::Parenthesis => ("(", ")"), - DelimiterKind::Invisible => ("$", "$"), - }; + pub fn subtree_as_debug_string(&self, subtree_idx: usize) -> String { + fn debug_subtree<S>( + output: &mut String, + subtree: &Subtree<S>, + iter: &mut std::slice::Iter<'_, TokenTree<S>>, + ) { + let delim = match subtree.delimiter.kind { + DelimiterKind::Brace => ("{", "}"), + DelimiterKind::Bracket => ("[", "]"), + DelimiterKind::Parenthesis => ("(", ")"), + DelimiterKind::Invisible => ("$", "$"), + }; - let mut res = String::new(); - res.push_str(delim.0); - let mut last = None; - for child in self.token_trees.iter() { - let s = match child { + output.push_str(delim.0); + let mut last = None; + let mut idx = 0; + while idx < subtree.len { + let child = iter.next().unwrap(); + debug_token_tree(output, child, last, iter); + last = Some(child); + idx += 1; + } + + output.push_str(delim.1); + } + + fn debug_token_tree<S>( + output: &mut String, + tt: &TokenTree<S>, + last: Option<&TokenTree<S>>, + iter: &mut std::slice::Iter<'_, TokenTree<S>>, + ) { + match tt { TokenTree::Leaf(it) => { let s = match it { Leaf::Literal(it) => it.symbol.to_string(), @@ -574,31 +920,37 @@ impl<S> Subtree<S> { }; match (it, last) { (Leaf::Ident(_), Some(&TokenTree::Leaf(Leaf::Ident(_)))) => { - " ".to_owned() + &s + output.push(' '); + output.push_str(&s); } (Leaf::Punct(_), Some(TokenTree::Leaf(Leaf::Punct(punct)))) => { if punct.spacing == Spacing::Alone { - " ".to_owned() + &s + output.push(' '); + output.push_str(&s); } else { - s + output.push_str(&s); } } - _ => s, + _ => output.push_str(&s), } } - TokenTree::Subtree(it) => it.as_debug_string(), - }; - res.push_str(&s); - last = Some(child); + TokenTree::Subtree(it) => debug_subtree(output, it, iter), + } } - res.push_str(delim.1); + let mut res = String::new(); + debug_token_tree( + &mut res, + &self.0[subtree_idx], + None, + &mut self.0[subtree_idx + 1..].iter(), + ); res } } -pub fn pretty<S>(tkns: &[TokenTree<S>]) -> String { - fn tokentree_to_text<S>(tkn: &TokenTree<S>) -> String { +pub fn pretty<S>(mut tkns: &[TokenTree<S>]) -> String { + fn tokentree_to_text<S>(tkn: &TokenTree<S>, tkns: &mut &[TokenTree<S>]) -> String { match tkn { TokenTree::Leaf(Leaf::Ident(ident)) => { format!("{}{}", ident.is_raw.as_str(), ident.sym) @@ -606,7 +958,9 @@ pub fn pretty<S>(tkns: &[TokenTree<S>]) -> String { TokenTree::Leaf(Leaf::Literal(literal)) => format!("{literal}"), TokenTree::Leaf(Leaf::Punct(punct)) => format!("{}", punct.char), TokenTree::Subtree(subtree) => { - let content = pretty(&subtree.token_trees); + let (subtree_content, rest) = tkns.split_at(subtree.usize_len()); + let content = pretty(subtree_content); + *tkns = rest; let (open, close) = match subtree.delimiter.kind { DelimiterKind::Brace => ("{", "}"), DelimiterKind::Bracket => ("[", "]"), @@ -618,16 +972,18 @@ pub fn pretty<S>(tkns: &[TokenTree<S>]) -> String { } } - tkns.iter() - .fold((String::new(), true), |(last, last_to_joint), tkn| { - let s = [last, tokentree_to_text(tkn)].join(if last_to_joint { "" } else { " " }); - let mut is_joint = false; - if let TokenTree::Leaf(Leaf::Punct(punct)) = tkn { - if punct.spacing == Spacing::Joint { - is_joint = true; - } + let mut last = String::new(); + let mut last_to_joint = true; + + while let Some((tkn, rest)) = tkns.split_first() { + tkns = rest; + last = [last, tokentree_to_text(tkn, &mut tkns)].join(if last_to_joint { "" } else { " " }); + last_to_joint = false; + if let TokenTree::Leaf(Leaf::Punct(punct)) = tkn { + if punct.spacing == Spacing::Joint { + last_to_joint = true; } - (s, is_joint) - }) - .0 + } + } + last } |