syntax/ast/make/
quote.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! 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_kind:ident $token_text:expr ]
        $($rest:tt)*
    ) => {
        $children.push($crate::ast::make::quote::NodeOrToken::Token(
            $crate::ast::make::quote::GreenToken::new(
                $crate::ast::make::quote::RSyntaxKind($crate::SyntaxKind::$token_kind as u16),
                &$token_text,
            ),
        ));
        $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 `['{']`. Alternatively, tokens can
/// be created with the syntax `[token_kind token_text]`, where `token_kind` is a variant of `SyntaxKind` (e.g.
/// `IDENT`) and `token_text` is an expression producing `String` or `&str`. 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)* } ) => {{
        #[allow(unused_mut)]
        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);
        }
    }
}

// This is useful when you want conditionally, based on some `bool`, to emit some code.
impl ToNodeChild for () {
    fn append_node_child(self, _children: &mut Vec<NodeOrToken<GreenNode, GreenToken>>) {}
}

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;
    }
}