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.rs310
1 files changed, 222 insertions, 88 deletions
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs
index 636f567f1a..91fcec9327 100644
--- a/crates/tt/src/lib.rs
+++ b/crates/tt/src/lib.rs
@@ -18,6 +18,7 @@ pub mod iter;
use std::fmt;
+use arrayvec::ArrayString;
use buffer::Cursor;
use intern::Symbol;
use stdx::{impl_from, itertools::Itertools as _};
@@ -111,7 +112,7 @@ impl Leaf {
}
impl_from!(Literal, Punct, Ident for Leaf);
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Subtree {
pub delimiter: Delimiter,
/// Number of following token trees that belong to this subtree, excluding this subtree.
@@ -125,7 +126,7 @@ impl Subtree {
}
#[derive(Clone, PartialEq, Eq, Hash)]
-pub struct TopSubtree(pub Box<[TokenTree]>);
+pub struct TopSubtree(Box<[TokenTree]>);
impl TopSubtree {
pub fn empty(span: DelimSpan) -> Self {
@@ -147,6 +148,10 @@ impl TopSubtree {
builder.build()
}
+ pub fn from_serialized(tt: Vec<TokenTree>) -> Self {
+ Self(tt.into_boxed_slice())
+ }
+
pub fn from_subtree(subtree: SubtreeView<'_>) -> Self {
Self(subtree.0.into())
}
@@ -159,20 +164,55 @@ impl TopSubtree {
self.view().iter()
}
- pub fn top_subtree(&self) -> &Subtree {
+ pub fn top_subtree(&self) -> Subtree {
self.view().top_subtree()
}
- pub fn top_subtree_delimiter_mut(&mut self) -> &mut Delimiter {
+ pub fn set_top_subtree_delimiter_kind(&mut self, kind: DelimiterKind) {
+ self.top_subtree_mut().delimiter.kind = kind;
+ }
+
+ pub fn set_top_subtree_delimiter_span(&mut self, span: DelimSpan) {
+ let top_subtree = self.top_subtree_mut();
+ top_subtree.delimiter.open = span.open;
+ top_subtree.delimiter.close = span.close;
+ }
+
+ fn top_subtree_mut(&mut self) -> &mut Subtree {
let TokenTree::Subtree(subtree) = &mut self.0[0] else {
unreachable!("the first token tree is always the top subtree");
};
- &mut subtree.delimiter
+ subtree
+ }
+
+ pub fn set_token(&mut self, idx: usize, leaf: Leaf) {
+ assert!(matches!(self.0[idx], TokenTree::Leaf(_)), "cannot replace a subtree by a leaf");
+ self.0[idx] = leaf.into();
}
pub fn token_trees(&self) -> TokenTreesView<'_> {
self.view().token_trees()
}
+
+ pub fn as_token_trees(&self) -> TokenTreesView<'_> {
+ self.view().as_token_trees()
+ }
+
+ pub fn change_every_ast_id(&mut self, mut callback: impl FnMut(&mut span::ErasedFileAstId)) {
+ for tt in &mut self.0 {
+ match tt {
+ TokenTree::Leaf(Leaf::Ident(Ident { span, .. }))
+ | TokenTree::Leaf(Leaf::Literal(Literal { span, .. }))
+ | TokenTree::Leaf(Leaf::Punct(Punct { span, .. })) => {
+ callback(&mut span.anchor.ast_id);
+ }
+ TokenTree::Subtree(subtree) => {
+ callback(&mut subtree.delimiter.open.anchor.ast_id);
+ callback(&mut subtree.delimiter.close.anchor.ast_id);
+ }
+ }
+ }
+ }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -241,11 +281,6 @@ impl TopSubtreeBuilder {
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>) {
- self.token_trees.extend(tt);
- }
-
pub fn extend_with_tt(&mut self, tt: TokenTreesView<'_>) {
self.token_trees.extend(tt.0.iter().cloned());
}
@@ -267,12 +302,12 @@ impl TopSubtreeBuilder {
}
}
- pub fn expected_delimiters(&self) -> impl Iterator<Item = &Delimiter> {
+ pub fn expected_delimiters(&self) -> impl Iterator<Item = DelimiterKind> {
self.unclosed_subtree_indices.iter().rev().map(|&subtree_idx| {
let TokenTree::Subtree(subtree) = &self.token_trees[subtree_idx] else {
unreachable!("unclosed token tree is always a subtree")
};
- &subtree.delimiter
+ subtree.delimiter.kind
})
}
@@ -330,7 +365,7 @@ pub struct SubtreeBuilderRestorePoint {
pub struct TokenTreesView<'a>(&'a [TokenTree]);
impl<'a> TokenTreesView<'a> {
- pub fn new(tts: &'a [TokenTree]) -> Self {
+ fn new(tts: &'a [TokenTree]) -> Self {
if cfg!(debug_assertions) {
tts.iter().enumerate().for_each(|(idx, tt)| {
if let TokenTree::Subtree(tt) = &tt {
@@ -345,12 +380,16 @@ impl<'a> TokenTreesView<'a> {
Self(tts)
}
+ pub fn empty() -> Self {
+ Self(&[])
+ }
+
pub fn iter(&self) -> TtIter<'a> {
TtIter::new(self.0)
}
pub fn cursor(&self) -> Cursor<'a> {
- Cursor::new(self.0)
+ Cursor::new(*self)
}
pub fn len(&self) -> usize {
@@ -374,13 +413,6 @@ impl<'a> TokenTreesView<'a> {
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] {
- self.0
- }
-
pub fn split(
self,
mut split_fn: impl FnMut(TtElement<'a>) -> bool,
@@ -406,6 +438,21 @@ impl<'a> TokenTreesView<'a> {
Some(result)
})
}
+
+ pub fn first_span(&self) -> Option<Span> {
+ Some(self.0.first()?.first_span())
+ }
+
+ pub fn last_span(&self) -> Option<Span> {
+ Some(match self.0.last()? {
+ TokenTree::Leaf(it) => *it.span(),
+ TokenTree::Subtree(it) => it.delimiter.close,
+ })
+ }
+
+ pub fn iter_flat_tokens(&self) -> impl ExactSizeIterator<Item = TokenTree> + use<'a> {
+ self.0.iter().cloned()
+ }
}
impl fmt::Debug for TokenTreesView<'_> {
@@ -453,11 +500,11 @@ impl fmt::Display for TokenTreesView<'_> {
match child {
TtElement::Leaf(Leaf::Punct(p)) => {
needs_space = p.spacing == Spacing::Alone;
- fmt::Display::fmt(p, f)?;
+ fmt::Display::fmt(&p, f)?;
}
- TtElement::Leaf(leaf) => fmt::Display::fmt(leaf, f)?,
+ TtElement::Leaf(leaf) => fmt::Display::fmt(&leaf, f)?,
TtElement::Subtree(subtree, subtree_iter) => {
- subtree_display(subtree, f, subtree_iter)?
+ subtree_display(&subtree, f, subtree_iter)?
}
}
}
@@ -493,11 +540,11 @@ impl<'a> SubtreeView<'a> {
TtIter::new(&self.0[1..])
}
- pub fn top_subtree(&self) -> &'a Subtree {
+ pub fn top_subtree(&self) -> Subtree {
let TokenTree::Subtree(subtree) = &self.0[0] else {
unreachable!("the first token tree is always the top subtree");
};
- subtree
+ *subtree
}
pub fn strip_invisible(&self) -> TokenTreesView<'a> {
@@ -571,17 +618,56 @@ pub enum DelimiterKind {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Literal {
- // escaped
- pub symbol: Symbol,
+ /// Escaped, text then suffix concatenated.
+ pub text_and_suffix: Symbol,
pub span: Span,
pub kind: LitKind,
- pub suffix: Option<Symbol>,
+ pub suffix_len: u8,
}
-pub fn token_to_literal(text: &str, span: Span) -> Literal
-where
- Span: Copy,
-{
+impl Literal {
+ #[inline]
+ pub fn text_and_suffix(&self) -> (&str, &str) {
+ let text_and_suffix = self.text_and_suffix.as_str();
+ text_and_suffix.split_at(text_and_suffix.len() - usize::from(self.suffix_len))
+ }
+
+ #[inline]
+ pub fn text(&self) -> &str {
+ self.text_and_suffix().0
+ }
+
+ #[inline]
+ pub fn suffix(&self) -> &str {
+ self.text_and_suffix().1
+ }
+
+ pub fn new(text: &str, span: Span, kind: LitKind, suffix: &str) -> Self {
+ const MAX_INLINE_CAPACITY: usize = 30;
+ let text_and_suffix = if suffix.is_empty() {
+ Symbol::intern(text)
+ } else if (text.len() + suffix.len()) < MAX_INLINE_CAPACITY {
+ let mut text_and_suffix = ArrayString::<MAX_INLINE_CAPACITY>::new();
+ text_and_suffix.push_str(text);
+ text_and_suffix.push_str(suffix);
+ Symbol::intern(&text_and_suffix)
+ } else {
+ let mut text_and_suffix = String::with_capacity(text.len() + suffix.len());
+ text_and_suffix.push_str(text);
+ text_and_suffix.push_str(suffix);
+ Symbol::intern(&text_and_suffix)
+ };
+
+ Self { text_and_suffix, span, kind, suffix_len: suffix.len().try_into().unwrap() }
+ }
+
+ #[inline]
+ pub fn new_no_suffix(text: &str, span: Span, kind: LitKind) -> Self {
+ Self { text_and_suffix: Symbol::intern(text), span, kind, suffix_len: 0 }
+ }
+}
+
+pub fn token_to_literal(text: &str, span: Span) -> Literal {
use rustc_lexer::LiteralKind;
let token = rustc_lexer::tokenize(text, rustc_lexer::FrontmatterAllowed::No).next_tuple();
@@ -590,12 +676,7 @@ where
..
},)) = token
else {
- return Literal {
- span,
- symbol: Symbol::intern(text),
- kind: LitKind::Err(()),
- suffix: None,
- };
+ return Literal::new_no_suffix(text, span, LitKind::Err(()));
};
let (kind, start_offset, end_offset) = match kind {
@@ -626,20 +707,15 @@ where
let (lit, suffix) = text.split_at(suffix_start as usize);
let lit = &lit[start_offset..lit.len() - end_offset];
let suffix = match suffix {
- "" | "_" => None,
+ "" | "_" => "",
// ill-suffixed literals
_ if !matches!(kind, LitKind::Integer | LitKind::Float | LitKind::Err(_)) => {
- return Literal {
- span,
- symbol: Symbol::intern(text),
- kind: LitKind::Err(()),
- suffix: None,
- };
+ return Literal::new_no_suffix(text, span, LitKind::Err(()));
}
- suffix => Some(Symbol::intern(suffix)),
+ suffix => suffix,
};
- Literal { span, symbol: Symbol::intern(lit), kind, suffix }
+ Literal::new(lit, span, kind, suffix)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -759,15 +835,8 @@ fn print_debug_token(f: &mut fmt::Formatter<'_>, level: usize, tt: TtElement<'_>
match tt {
TtElement::Leaf(leaf) => match leaf {
Leaf::Literal(lit) => {
- write!(
- f,
- "{}LITERAL {:?} {}{} {:#?}",
- align,
- lit.kind,
- lit.symbol,
- lit.suffix.as_ref().map(|it| it.as_str()).unwrap_or(""),
- lit.span
- )?;
+ let (text, suffix) = lit.text_and_suffix();
+ write!(f, "{}LITERAL {:?} {}{} {:#?}", align, lit.kind, text, suffix, lit.span)?;
}
Leaf::Punct(punct) => {
write!(
@@ -791,7 +860,7 @@ fn print_debug_token(f: &mut fmt::Formatter<'_>, level: usize, tt: TtElement<'_>
}
},
TtElement::Subtree(subtree, subtree_iter) => {
- print_debug_subtree(f, subtree, level, subtree_iter)?;
+ print_debug_subtree(f, &subtree, level, subtree_iter)?;
}
}
@@ -829,44 +898,28 @@ impl fmt::Display for Ident {
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let (text, suffix) = self.text_and_suffix();
match self.kind {
- LitKind::Byte => write!(f, "b'{}'", self.symbol),
- LitKind::Char => write!(f, "'{}'", self.symbol),
- LitKind::Integer | LitKind::Float | LitKind::Err(_) => write!(f, "{}", self.symbol),
- LitKind::Str => write!(f, "\"{}\"", self.symbol),
- LitKind::ByteStr => write!(f, "b\"{}\"", self.symbol),
- LitKind::CStr => write!(f, "c\"{}\"", self.symbol),
+ LitKind::Byte => write!(f, "b'{}'", text),
+ LitKind::Char => write!(f, "'{}'", text),
+ LitKind::Integer | LitKind::Float | LitKind::Err(_) => write!(f, "{}", text),
+ LitKind::Str => write!(f, "\"{}\"", text),
+ LitKind::ByteStr => write!(f, "b\"{}\"", text),
+ LitKind::CStr => write!(f, "c\"{}\"", text),
LitKind::StrRaw(num_of_hashes) => {
let num_of_hashes = num_of_hashes as usize;
- write!(
- f,
- r#"r{0:#<num_of_hashes$}"{text}"{0:#<num_of_hashes$}"#,
- "",
- text = self.symbol
- )
+ write!(f, r#"r{0:#<num_of_hashes$}"{text}"{0:#<num_of_hashes$}"#, "", text = text)
}
LitKind::ByteStrRaw(num_of_hashes) => {
let num_of_hashes = num_of_hashes as usize;
- write!(
- f,
- r#"br{0:#<num_of_hashes$}"{text}"{0:#<num_of_hashes$}"#,
- "",
- text = self.symbol
- )
+ write!(f, r#"br{0:#<num_of_hashes$}"{text}"{0:#<num_of_hashes$}"#, "", text = text)
}
LitKind::CStrRaw(num_of_hashes) => {
let num_of_hashes = num_of_hashes as usize;
- write!(
- f,
- r#"cr{0:#<num_of_hashes$}"{text}"{0:#<num_of_hashes$}"#,
- "",
- text = self.symbol
- )
+ write!(f, r#"cr{0:#<num_of_hashes$}"{text}"{0:#<num_of_hashes$}"#, "", text = text)
}
}?;
- if let Some(suffix) = &self.suffix {
- write!(f, "{suffix}")?;
- }
+ write!(f, "{suffix}")?;
Ok(())
}
}
@@ -921,7 +974,7 @@ impl TopSubtree {
match tt {
TokenTree::Leaf(it) => {
let s = match it {
- Leaf::Literal(it) => it.symbol.to_string(),
+ Leaf::Literal(it) => it.text().to_owned(),
Leaf::Punct(it) => it.char.to_string(),
Leaf::Ident(it) => format!("{}{}", it.is_raw.as_str(), it.sym),
};
@@ -956,7 +1009,7 @@ impl TopSubtree {
}
}
-pub fn pretty(mut tkns: &[TokenTree]) -> String {
+pub fn pretty(tkns: TokenTreesView<'_>) -> String {
fn tokentree_to_text(tkn: &TokenTree, tkns: &mut &[TokenTree]) -> String {
match tkn {
TokenTree::Leaf(Leaf::Ident(ident)) => {
@@ -966,7 +1019,7 @@ pub fn pretty(mut tkns: &[TokenTree]) -> String {
TokenTree::Leaf(Leaf::Punct(punct)) => format!("{}", punct.char),
TokenTree::Subtree(subtree) => {
let (subtree_content, rest) = tkns.split_at(subtree.usize_len());
- let content = pretty(subtree_content);
+ let content = pretty(TokenTreesView(subtree_content));
*tkns = rest;
let (open, close) = match subtree.delimiter.kind {
DelimiterKind::Brace => ("{", "}"),
@@ -979,6 +1032,7 @@ pub fn pretty(mut tkns: &[TokenTree]) -> String {
}
}
+ let mut tkns = tkns.0;
let mut last = String::new();
let mut last_to_joint = true;
@@ -994,3 +1048,83 @@ pub fn pretty(mut tkns: &[TokenTree]) -> String {
}
last
}
+
+#[derive(Debug)]
+pub enum TransformTtAction<'a> {
+ Keep,
+ ReplaceWith(TokenTreesView<'a>),
+}
+
+impl TransformTtAction<'_> {
+ #[inline]
+ pub fn remove() -> Self {
+ Self::ReplaceWith(TokenTreesView::empty())
+ }
+}
+
+/// This function takes a token tree, and calls `callback` with each token tree in it.
+/// Then it does what the callback says: keeps the tt or replaces it with a (possibly empty)
+/// tts view.
+pub fn transform_tt<'b>(
+ tt: &mut TopSubtree,
+ mut callback: impl FnMut(TokenTree) -> TransformTtAction<'b>,
+) {
+ let mut tt_vec = std::mem::take(&mut tt.0).into_vec();
+
+ // We need to keep a stack of the currently open subtrees, because we need to update
+ // them if we change the number of items in them.
+ let mut subtrees_stack = Vec::new();
+ let mut i = 0;
+ while i < tt_vec.len() {
+ 'pop_finished_subtrees: while let Some(&subtree_idx) = subtrees_stack.last() {
+ let TokenTree::Subtree(subtree) = &tt_vec[subtree_idx] else {
+ unreachable!("non-subtree on subtrees stack");
+ };
+ if i >= subtree_idx + 1 + subtree.usize_len() {
+ subtrees_stack.pop();
+ } else {
+ break 'pop_finished_subtrees;
+ }
+ }
+
+ let current = match &tt_vec[i] {
+ TokenTree::Leaf(leaf) => TokenTree::Leaf(match leaf {
+ Leaf::Literal(leaf) => Leaf::Literal(leaf.clone()),
+ Leaf::Punct(leaf) => Leaf::Punct(*leaf),
+ Leaf::Ident(leaf) => Leaf::Ident(leaf.clone()),
+ }),
+ TokenTree::Subtree(subtree) => TokenTree::Subtree(*subtree),
+ };
+ let action = callback(current);
+ match action {
+ TransformTtAction::Keep => {
+ // This cannot be shared with the replaced case, because then we may push the same subtree
+ // twice, and will update it twice which will lead to errors.
+ if let TokenTree::Subtree(_) = &tt_vec[i] {
+ subtrees_stack.push(i);
+ }
+
+ i += 1;
+ }
+ TransformTtAction::ReplaceWith(replacement) => {
+ let old_len = 1 + match &tt_vec[i] {
+ TokenTree::Leaf(_) => 0,
+ TokenTree::Subtree(subtree) => subtree.usize_len(),
+ };
+ let len_diff = replacement.len() as i64 - old_len as i64;
+ tt_vec.splice(i..i + old_len, replacement.0.iter().cloned());
+ // Skip the newly inserted replacement, we don't want to visit it.
+ i += replacement.len();
+
+ for &subtree_idx in &subtrees_stack {
+ let TokenTree::Subtree(subtree) = &mut tt_vec[subtree_idx] else {
+ unreachable!("non-subtree on subtrees stack");
+ };
+ subtree.len = (i64::from(subtree.len) + len_diff).try_into().unwrap();
+ }
+ }
+ }
+ }
+
+ tt.0 = tt_vec.into_boxed_slice();
+}