//! 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>); } impl ToNodeChild for N { fn append_node_child(self, children: &mut Vec>) { children.push((*self.syntax().clone_subtree().green()).to_owned().into()); } } impl ToNodeChild for Option { fn append_node_child(self, children: &mut Vec>) { 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>) {} } 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; } }