syntax/ast/make/
quote.rs

1//! A `quote!`-like API for crafting AST nodes.
2
3pub(crate) use rowan::{GreenNode, GreenToken, NodeOrToken, SyntaxKind as RSyntaxKind};
4
5macro_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_kind:ident $token_text:expr ]
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::SyntaxKind::$token_kind as u16),
37                &$token_text,
38            ),
39        ));
40        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
41    };
42
43    ( @append $children:ident
44        [$($token:tt)+]
45        $($rest:tt)*
46    ) => {
47        $children.push($crate::ast::make::quote::NodeOrToken::Token(
48            $crate::ast::make::quote::GreenToken::new(
49                $crate::ast::make::quote::RSyntaxKind($crate::T![ $($token)+ ] as u16),
50                const { $crate::T![ $($token)+ ].text() },
51            ),
52        ));
53        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
54    };
55
56    ( @append $children:ident
57        $whitespace:literal
58        $($rest:tt)*
59    ) => {
60        const { $crate::ast::make::quote::verify_only_whitespaces($whitespace) };
61        $children.push($crate::ast::make::quote::NodeOrToken::Token(
62            $crate::ast::make::quote::GreenToken::new(
63                $crate::ast::make::quote::RSyntaxKind($crate::SyntaxKind::WHITESPACE as u16),
64                $whitespace,
65            ),
66        ));
67        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
68    };
69
70    ( @append $children:ident
71        # $var:ident
72        $($rest:tt)*
73    ) => {
74        $crate::ast::make::quote::ToNodeChild::append_node_child($var, &mut $children);
75        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
76    };
77
78    ( @append $children:ident
79        #( $($repetition:tt)+ )*
80        $($rest:tt)*
81    ) => {
82        $crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
83            [] [] $($repetition)*
84        );
85        $crate::ast::make::quote::quote_impl!( @append $children $($rest)* );
86    };
87
88    // Base case - no repetition var.
89    ( @extract_pounded_in_repetition $children:ident
90        [ $($repetition:tt)* ] [ ]
91    ) => {
92        ::std::compile_error!("repetition in `ast::make::quote!()` without variable");
93    };
94
95    // Base case - repetition var found.
96    ( @extract_pounded_in_repetition $children:ident
97        [ $($repetition:tt)* ] [ $repetition_var:ident ]
98    ) => {
99        ::std::iter::IntoIterator::into_iter($repetition_var).for_each(|$repetition_var| {
100            $crate::ast::make::quote::quote_impl!( @append $children $($repetition)* );
101        });
102    };
103
104    ( @extract_pounded_in_repetition $children:ident
105        [ $($repetition:tt)* ] [ $repetition_var1:ident ] # $repetition_var2:ident $($rest:tt)*
106    ) => {
107        ::std::compile_error!("repetition in `ast::make::quote!()` with more than one variable");
108    };
109
110    ( @extract_pounded_in_repetition $children:ident
111        [ $($repetition:tt)* ] [ ] # $repetition_var:ident $($rest:tt)*
112    ) => {
113        $crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
114            [ $($repetition)* # $repetition_var ] [ $repetition_var ] $($rest)*
115        );
116    };
117
118    ( @extract_pounded_in_repetition $children:ident
119        [ $($repetition:tt)* ] [ $($repetition_var:tt)* ] $non_repetition_var:tt $($rest:tt)*
120    ) => {
121        $crate::ast::make::quote::quote_impl!( @extract_pounded_in_repetition $children
122            [ $($repetition)* $non_repetition_var ] [ $($repetition_var)* ] $($rest)*
123        );
124    };
125}
126pub(crate) use quote_impl_ as quote_impl;
127
128/// A `quote!`-like API for crafting AST nodes.
129///
130/// Syntax: AST nodes are created with `Node { children }`, where `Node` is the node name in `ast` (`ast::Node`).
131/// Tokens are creates with their syntax enclosed by brackets, e.g. `[::]` or `['{']`. Alternatively, tokens can
132/// be created with the syntax `[token_kind token_text]`, where `token_kind` is a variant of `SyntaxKind` (e.g.
133/// `IDENT`) and `token_text` is an expression producing `String` or `&str`. Whitespaces can be added
134/// as string literals (i.e. `"\n    "` is a whitespace token). Interpolation is allowed with `#` (`#variable`),
135/// from `AstNode`s and `Option`s of them. Repetition is also supported, with only one repeating variable
136/// and no separator (`#("\n" #variable [>])*`), for any `IntoIterator`. Note that `Option`s are also `IntoIterator`,
137/// which can help when you want to conditionally include something along with an optional node.
138///
139/// There needs to be one root node, and its type is returned.
140///
141/// Be careful to closely match the Ungrammar AST, there is no validation for this!
142macro_rules! quote_ {
143    ( $root:ident { $($tree:tt)* } ) => {{
144        #[allow(unused_mut)]
145        let mut root = ::std::vec::Vec::<$crate::ast::make::quote::NodeOrToken<
146            $crate::ast::make::quote::GreenNode,
147            $crate::ast::make::quote::GreenToken,
148        >>::with_capacity(1);
149        $crate::ast::make::quote::quote_impl!( @append root $root { $($tree)* } );
150        let root = root.into_iter().next().unwrap();
151        let root = $crate::SyntaxNode::new_root(root.into_node().unwrap());
152        <$crate::ast::$root as $crate::ast::AstNode>::cast(root).unwrap()
153    }};
154}
155pub(crate) use quote_ as quote;
156
157use crate::AstNode;
158
159pub(crate) trait ToNodeChild {
160    fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>);
161}
162
163impl<N: AstNode> ToNodeChild for N {
164    fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {
165        children.push((*self.syntax().clone_subtree().green()).to_owned().into());
166    }
167}
168
169impl<C: ToNodeChild> ToNodeChild for Option<C> {
170    fn append_node_child(self, children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {
171        if let Some(child) = self {
172            child.append_node_child(children);
173        }
174    }
175}
176
177// This is useful when you want conditionally, based on some `bool`, to emit some code.
178impl ToNodeChild for () {
179    fn append_node_child(self, _children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {}
180}
181
182pub(crate) const fn verify_only_whitespaces(text: &str) {
183    let text = text.as_bytes();
184    let mut i = 0;
185    while i < text.len() {
186        if !text[i].is_ascii_whitespace() {
187            panic!("non-whitespace found in whitespace token");
188        }
189        i += 1;
190    }
191}