Skip to content

Commit a0c052f

Browse files
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.
1 parent be12c80 commit a0c052f

File tree

2 files changed

+182
-9
lines changed

2 files changed

+182
-9
lines changed

src/tools/rust-analyzer/crates/syntax/src/ast/make.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
//! `parse(format!())` we use internally is an implementation detail -- long
1111
//! term, it will be replaced with direct tree manipulation.
1212
13+
mod quote;
14+
1315
use itertools::Itertools;
1416
use parser::{Edition, T};
1517
use rowan::NodeOrToken;
1618
use stdx::{format_to, format_to_acc, never};
1719

1820
use crate::{
19-
ast::{self, Param},
21+
ast::{self, make::quote::quote, Param},
2022
utils::is_raw_identifier,
2123
AstNode, SourceFile, SyntaxKind, SyntaxToken,
2224
};
@@ -480,15 +482,16 @@ pub fn block_expr(
480482
stmts: impl IntoIterator<Item = ast::Stmt>,
481483
tail_expr: Option<ast::Expr>,
482484
) -> ast::BlockExpr {
483-
let mut buf = "{\n".to_owned();
484-
for stmt in stmts.into_iter() {
485-
format_to!(buf, " {stmt}\n");
486-
}
487-
if let Some(tail_expr) = tail_expr {
488-
format_to!(buf, " {tail_expr}\n");
485+
quote! {
486+
BlockExpr {
487+
StmtList {
488+
['{'] "\n"
489+
#(" " #stmts "\n")*
490+
#(" " #tail_expr "\n")*
491+
['}']
492+
}
493+
}
489494
}
490-
buf += "}";
491-
ast_from_text(&format!("fn f() {buf}"))
492495
}
493496

494497
pub fn async_move_block_expr(
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//! A `quote!`-like API for crafting AST nodes.
2+
3+
pub(crate) use rowan::{GreenNode, GreenToken, NodeOrToken, SyntaxKind as RSyntaxKind};
4+
5+
macro_rules! quote_impl_ {
6+
( @append $children:ident ) => {}; // Base case.
7+
8+
( @append $children:ident
9+
$node:ident {
10+
$($tree:tt)*
11+
}
12+
$($rest:tt)*
13+
) => {
14+
{
15+
#[allow(unused_mut)]
16+
let mut inner_children = ::std::vec::Vec::<$crate::ast::make::quote::NodeOrToken<
17+
$crate::ast::make::quote::GreenNode,
18+
$crate::ast::make::quote::GreenToken,
19+
>>::new();
20+
$crate::ast::make::quote::quote_impl!( @append inner_children
21+
$($tree)*
22+
);
23+
let kind = <$crate::ast::$node as $crate::ast::AstNode>::kind();
24+
let node = $crate::ast::make::quote::GreenNode::new($crate::ast::make::quote::RSyntaxKind(kind as u16), inner_children);
25+
$children.push($crate::ast::make::quote::NodeOrToken::Node(node));
26+
}
27+
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
28+
};
29+
30+
( @append $children:ident
31+
[$($token:tt)+]
32+
$($rest:tt)*
33+
) => {
34+
$children.push($crate::ast::make::quote::NodeOrToken::Token(
35+
$crate::ast::make::quote::GreenToken::new(
36+
$crate::ast::make::quote::RSyntaxKind($crate::T![ $($token)+ ] as u16),
37+
const { $crate::T![ $($token)+ ].text() },
38+
),
39+
));
40+
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
41+
};
42+
43+
( @append $children:ident
44+
$whitespace:literal
45+
$($rest:tt)*
46+
) => {
47+
const { $crate::ast::make::quote::verify_only_whitespaces($whitespace) };
48+
$children.push($crate::ast::make::quote::NodeOrToken::Token(
49+
$crate::ast::make::quote::GreenToken::new(
50+
$crate::ast::make::quote::RSyntaxKind($crate::SyntaxKind::WHITESPACE as u16),
51+
$whitespace,
52+
),
53+
));
54+
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
55+
};
56+
57+
( @append $children:ident
58+
# $var:ident
59+
$($rest:tt)*
60+
) => {
61+
$crate::ast::make::quote::ToNodeChild::append_node_child($var, &mut $children);
62+
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
63+
};
64+
65+
( @append $children:ident
66+
#( $($repetition:tt)+ )*
67+
$($rest:tt)*
68+
) => {
69+
$crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
70+
[] [] $($repetition)*
71+
);
72+
$crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
73+
};
74+
75+
// Base case - no repetition var.
76+
( @extract_pounded_in_repetition $children:ident
77+
[ $($repetition:tt)* ] [ ]
78+
) => {
79+
::std::compile_error!("repetition in `ast::make::quote!()` without variable");
80+
};
81+
82+
// Base case - repetition var found.
83+
( @extract_pounded_in_repetition $children:ident
84+
[ $($repetition:tt)* ] [ $repetition_var:ident ]
85+
) => {
86+
::std::iter::IntoIterator::into_iter($repetition_var).for_each(|$repetition_var| {
87+
$crate::ast::make::quote::quote_impl!( @append $children $($repetition)* );
88+
});
89+
};
90+
91+
( @extract_pounded_in_repetition $children:ident
92+
[ $($repetition:tt)* ] [ $repetition_var1:ident ] # $repetition_var2:ident $($rest:tt)*
93+
) => {
94+
::std::compile_error!("repetition in `ast::make::quote!()` with more than one variable");
95+
};
96+
97+
( @extract_pounded_in_repetition $children:ident
98+
[ $($repetition:tt)* ] [ ] # $repetition_var:ident $($rest:tt)*
99+
) => {
100+
$crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
101+
[ $($repetition)* # $repetition_var ] [ $repetition_var ] $($rest)*
102+
);
103+
};
104+
105+
( @extract_pounded_in_repetition $children:ident
106+
[ $($repetition:tt)* ] [ $($repetition_var:tt)* ] $non_repetition_var:tt $($rest:tt)*
107+
) => {
108+
$crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
109+
[ $($repetition)* $non_repetition_var ] [ $($repetition_var)* ] $($rest)*
110+
);
111+
};
112+
}
113+
pub(crate) use quote_impl_ as quote_impl;
114+
115+
/// A `quote!`-like API for crafting AST nodes.
116+
///
117+
/// Syntax: AST nodes are created with `Node { children }`, where `Node` is the node name in `ast` (`ast::Node`).
118+
/// Tokens are creates with their syntax enclosed by brackets, e.g. `[::]` or `['{']`. Whitespaces can be added
119+
/// as string literals (i.e. `"\n "` is a whitespace token). Interpolation is allowed with `#` (`#variable`),
120+
/// from `AstNode`s and `Option`s of them. Repetition is also supported, with only one repeating variable
121+
/// and no separator (`#("\n" #variable [>])*`), for any `IntoIterator`. Note that `Option`s are also `IntoIterator`,
122+
/// which can help when you want to conditionally include something along with an optional node.
123+
///
124+
/// There needs to be one root node, and its type is returned.
125+
///
126+
/// Be careful to closely match the Ungrammar AST, there is no validation for this!
127+
macro_rules! quote_ {
128+
( $root:ident { $($tree:tt)* } ) => {{
129+
let mut root = ::std::vec::Vec::<$crate::ast::make::quote::NodeOrToken<
130+
$crate::ast::make::quote::GreenNode,
131+
$crate::ast::make::quote::GreenToken,
132+
>>::with_capacity(1);
133+
$crate::ast::make::quote::quote_impl!( @append root $root { $($tree)* } );
134+
let root = root.into_iter().next().unwrap();
135+
let root = $crate::SyntaxNode::new_root(root.into_node().unwrap());
136+
<$crate::ast::$root as $crate::ast::AstNode>::cast(root).unwrap()
137+
}};
138+
}
139+
pub(crate) use quote_ as quote;
140+
141+
use crate::AstNode;
142+
143+
pub(crate) trait ToNodeChild {
144+
fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>);
145+
}
146+
147+
impl<N: AstNode> ToNodeChild for N {
148+
fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {
149+
children.push(self.syntax().clone_subtree().green().to_owned().into());
150+
}
151+
}
152+
153+
impl<C: ToNodeChild> ToNodeChild for Option<C> {
154+
fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {
155+
if let Some(child) = self {
156+
child.append_node_child(children);
157+
}
158+
}
159+
}
160+
161+
pub(crate) const fn verify_only_whitespaces(text: &str) {
162+
let text = text.as_bytes();
163+
let mut i = 0;
164+
while i < text.len() {
165+
if !text[i].is_ascii_whitespace() {
166+
panic!("non-whitespace found in whitespace token");
167+
}
168+
i += 1;
169+
}
170+
}

0 commit comments

Comments
 (0)