mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-11 20:58:54 +00:00
Create a quote!
-like API for crafting AST nodes
Instead of messing with textual `make`. And port one `make` helper to it, for the sake of testing.
This commit is contained in:
parent
505e82c19c
commit
abd7263179
2 changed files with 182 additions and 9 deletions
|
@ -10,13 +10,15 @@
|
|||
//! `parse(format!())` we use internally is an implementation detail -- long
|
||||
//! term, it will be replaced with direct tree manipulation.
|
||||
|
||||
mod quote;
|
||||
|
||||
use itertools::Itertools;
|
||||
use parser::{Edition, T};
|
||||
use rowan::NodeOrToken;
|
||||
use stdx::{format_to, format_to_acc, never};
|
||||
|
||||
use crate::{
|
||||
ast::{self, Param},
|
||||
ast::{self, make::quote::quote, Param},
|
||||
utils::is_raw_identifier,
|
||||
AstNode, SourceFile, SyntaxKind, SyntaxToken,
|
||||
};
|
||||
|
@ -480,15 +482,16 @@ pub fn block_expr(
|
|||
stmts: impl IntoIterator<Item = ast::Stmt>,
|
||||
tail_expr: Option<ast::Expr>,
|
||||
) -> ast::BlockExpr {
|
||||
let mut buf = "{\n".to_owned();
|
||||
for stmt in stmts.into_iter() {
|
||||
format_to!(buf, " {stmt}\n");
|
||||
quote! {
|
||||
BlockExpr {
|
||||
StmtList {
|
||||
['{'] "\n"
|
||||
#(" " #stmts "\n")*
|
||||
#(" " #tail_expr "\n")*
|
||||
['}']
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(tail_expr) = tail_expr {
|
||||
format_to!(buf, " {tail_expr}\n");
|
||||
}
|
||||
buf += "}";
|
||||
ast_from_text(&format!("fn f() {buf}"))
|
||||
}
|
||||
|
||||
pub fn async_move_block_expr(
|
||||
|
|
170
crates/syntax/src/ast/make/quote.rs
Normal file
170
crates/syntax/src/ast/make/quote.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
//! A `quote!`-like API for crafting AST nodes.
|
||||
|
||||
pub(crate) use rowan::{GreenNode, GreenToken, NodeOrToken, SyntaxKind as RSyntaxKind};
|
||||
|
||||
macro_rules! quote_impl_ {
|
||||
( @append $children:ident ) => {}; // Base case.
|
||||
|
||||
( @append $children:ident
|
||||
$node:ident {
|
||||
$($tree:tt)*
|
||||
}
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut inner_children = ::std::vec::Vec::<$crate::ast::make::quote::NodeOrToken<
|
||||
$crate::ast::make::quote::GreenNode,
|
||||
$crate::ast::make::quote::GreenToken,
|
||||
>>::new();
|
||||
$crate::ast::make::quote::quote_impl!( @append inner_children
|
||||
$($tree)*
|
||||
);
|
||||
let kind = <$crate::ast::$node as $crate::ast::AstNode>::kind();
|
||||
let node = $crate::ast::make::quote::GreenNode::new($crate::ast::make::quote::RSyntaxKind(kind as u16), inner_children);
|
||||
$children.push($crate::ast::make::quote::NodeOrToken::Node(node));
|
||||
}
|
||||
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
|
||||
};
|
||||
|
||||
( @append $children:ident
|
||||
[$($token:tt)+]
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
$children.push($crate::ast::make::quote::NodeOrToken::Token(
|
||||
$crate::ast::make::quote::GreenToken::new(
|
||||
$crate::ast::make::quote::RSyntaxKind($crate::T![ $($token)+ ] as u16),
|
||||
const { $crate::T![ $($token)+ ].text() },
|
||||
),
|
||||
));
|
||||
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
|
||||
};
|
||||
|
||||
( @append $children:ident
|
||||
$whitespace:literal
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
const { $crate::ast::make::quote::verify_only_whitespaces($whitespace) };
|
||||
$children.push($crate::ast::make::quote::NodeOrToken::Token(
|
||||
$crate::ast::make::quote::GreenToken::new(
|
||||
$crate::ast::make::quote::RSyntaxKind($crate::SyntaxKind::WHITESPACE as u16),
|
||||
$whitespace,
|
||||
),
|
||||
));
|
||||
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
|
||||
};
|
||||
|
||||
( @append $children:ident
|
||||
# $var:ident
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
$crate::ast::make::quote::ToNodeChild::append_node_child($var, &mut $children);
|
||||
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
|
||||
};
|
||||
|
||||
( @append $children:ident
|
||||
#( $($repetition:tt)+ )*
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
$crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
|
||||
[] [] $($repetition)*
|
||||
);
|
||||
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
|
||||
};
|
||||
|
||||
// Base case - no repetition var.
|
||||
( @extract_pounded_in_repetition $children:ident
|
||||
[ $($repetition:tt)* ] [ ]
|
||||
) => {
|
||||
::std::compile_error!("repetition in `ast::make::quote!()` without variable");
|
||||
};
|
||||
|
||||
// Base case - repetition var found.
|
||||
( @extract_pounded_in_repetition $children:ident
|
||||
[ $($repetition:tt)* ] [ $repetition_var:ident ]
|
||||
) => {
|
||||
::std::iter::IntoIterator::into_iter($repetition_var).for_each(|$repetition_var| {
|
||||
$crate::ast::make::quote::quote_impl!( @append $children $($repetition)* );
|
||||
});
|
||||
};
|
||||
|
||||
( @extract_pounded_in_repetition $children:ident
|
||||
[ $($repetition:tt)* ] [ $repetition_var1:ident ] # $repetition_var2:ident $($rest:tt)*
|
||||
) => {
|
||||
::std::compile_error!("repetition in `ast::make::quote!()` with more than one variable");
|
||||
};
|
||||
|
||||
( @extract_pounded_in_repetition $children:ident
|
||||
[ $($repetition:tt)* ] [ ] # $repetition_var:ident $($rest:tt)*
|
||||
) => {
|
||||
$crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
|
||||
[ $($repetition)* # $repetition_var ] [ $repetition_var ] $($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
( @extract_pounded_in_repetition $children:ident
|
||||
[ $($repetition:tt)* ] [ $($repetition_var:tt)* ] $non_repetition_var:tt $($rest:tt)*
|
||||
) => {
|
||||
$crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
|
||||
[ $($repetition)* $non_repetition_var ] [ $($repetition_var)* ] $($rest)*
|
||||
);
|
||||
};
|
||||
}
|
||||
pub(crate) use quote_impl_ as quote_impl;
|
||||
|
||||
/// A `quote!`-like API for crafting AST nodes.
|
||||
///
|
||||
/// Syntax: AST nodes are created with `Node { children }`, where `Node` is the node name in `ast` (`ast::Node`).
|
||||
/// Tokens are creates with their syntax enclosed by brackets, e.g. `[::]` or `['{']`. Whitespaces can be added
|
||||
/// as string literals (i.e. `"\n "` is a whitespace token). Interpolation is allowed with `#` (`#variable`),
|
||||
/// from `AstNode`s and `Option`s of them. Repetition is also supported, with only one repeating variable
|
||||
/// and no separator (`#("\n" #variable [>])*`), for any `IntoIterator`. Note that `Option`s are also `IntoIterator`,
|
||||
/// which can help when you want to conditionally include something along with an optional node.
|
||||
///
|
||||
/// There needs to be one root node, and its type is returned.
|
||||
///
|
||||
/// Be careful to closely match the Ungrammar AST, there is no validation for this!
|
||||
macro_rules! quote_ {
|
||||
( $root:ident { $($tree:tt)* } ) => {{
|
||||
let mut root = ::std::vec::Vec::<$crate::ast::make::quote::NodeOrToken<
|
||||
$crate::ast::make::quote::GreenNode,
|
||||
$crate::ast::make::quote::GreenToken,
|
||||
>>::with_capacity(1);
|
||||
$crate::ast::make::quote::quote_impl!( @append root $root { $($tree)* } );
|
||||
let root = root.into_iter().next().unwrap();
|
||||
let root = $crate::SyntaxNode::new_root(root.into_node().unwrap());
|
||||
<$crate::ast::$root as $crate::ast::AstNode>::cast(root).unwrap()
|
||||
}};
|
||||
}
|
||||
pub(crate) use quote_ as quote;
|
||||
|
||||
use crate::AstNode;
|
||||
|
||||
pub(crate) trait ToNodeChild {
|
||||
fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>);
|
||||
}
|
||||
|
||||
impl<N: AstNode> ToNodeChild for N {
|
||||
fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {
|
||||
children.push(self.syntax().clone_subtree().green().to_owned().into());
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ToNodeChild> ToNodeChild for Option<C> {
|
||||
fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {
|
||||
if let Some(child) = self {
|
||||
child.append_node_child(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn verify_only_whitespaces(text: &str) {
|
||||
let text = text.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < text.len() {
|
||||
if !text[i].is_ascii_whitespace() {
|
||||
panic!("non-whitespace found in whitespace token");
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue