Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/syntax/src/ast/make/quote.rs')
| -rw-r--r-- | crates/syntax/src/ast/make/quote.rs | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/crates/syntax/src/ast/make/quote.rs b/crates/syntax/src/ast/make/quote.rs new file mode 100644 index 0000000000..300ef25c13 --- /dev/null +++ b/crates/syntax/src/ast/make/quote.rs @@ -0,0 +1,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; + } +} |