diff options
Diffstat (limited to 'crates/ra_mbe/src')
-rw-r--r-- | crates/ra_mbe/src/lib.rs | 9 | ||||
-rw-r--r-- | crates/ra_mbe/src/mbe_expander.rs | 2 | ||||
-rw-r--r-- | crates/ra_mbe/src/syntax_bridge.rs | 113 |
3 files changed, 122 insertions, 2 deletions
diff --git a/crates/ra_mbe/src/lib.rs b/crates/ra_mbe/src/lib.rs index 38bf3431a..c7be33b19 100644 --- a/crates/ra_mbe/src/lib.rs +++ b/crates/ra_mbe/src/lib.rs | |||
@@ -1,3 +1,8 @@ | |||
1 | /// `mbe` (short for Macro By Example) crate contains code for handling | ||
2 | /// `macro_rules` macros. It uses `TokenTree` (from `ra_tt` package) as the | ||
3 | /// interface, although it contains some code to bridge `SyntaxNode`s and | ||
4 | /// `TokenTree`s as well! | ||
5 | |||
1 | macro_rules! impl_froms { | 6 | macro_rules! impl_froms { |
2 | ($e:ident: $($v:ident), *) => { | 7 | ($e:ident: $($v:ident), *) => { |
3 | $( | 8 | $( |
@@ -13,14 +18,16 @@ macro_rules! impl_froms { | |||
13 | mod tt_cursor; | 18 | mod tt_cursor; |
14 | mod mbe_parser; | 19 | mod mbe_parser; |
15 | mod mbe_expander; | 20 | mod mbe_expander; |
21 | mod syntax_bridge; | ||
16 | 22 | ||
17 | use smol_str::SmolStr; | 23 | use ra_syntax::SmolStr; |
18 | 24 | ||
19 | pub use tt::{Delimiter, Punct}; | 25 | pub use tt::{Delimiter, Punct}; |
20 | 26 | ||
21 | pub use crate::{ | 27 | pub use crate::{ |
22 | mbe_parser::parse, | 28 | mbe_parser::parse, |
23 | mbe_expander::exapnd, | 29 | mbe_expander::exapnd, |
30 | syntax_bridge::macro_call_to_tt, | ||
24 | }; | 31 | }; |
25 | 32 | ||
26 | #[derive(Debug)] | 33 | #[derive(Debug)] |
diff --git a/crates/ra_mbe/src/mbe_expander.rs b/crates/ra_mbe/src/mbe_expander.rs index 92ad26889..21c1552ce 100644 --- a/crates/ra_mbe/src/mbe_expander.rs +++ b/crates/ra_mbe/src/mbe_expander.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use rustc_hash::FxHashMap; | 1 | use rustc_hash::FxHashMap; |
2 | use smol_str::SmolStr; | 2 | use ra_syntax::SmolStr; |
3 | 3 | ||
4 | use crate::{self as mbe, tt_cursor::TtCursor}; | 4 | use crate::{self as mbe, tt_cursor::TtCursor}; |
5 | 5 | ||
diff --git a/crates/ra_mbe/src/syntax_bridge.rs b/crates/ra_mbe/src/syntax_bridge.rs new file mode 100644 index 000000000..aad5f24b7 --- /dev/null +++ b/crates/ra_mbe/src/syntax_bridge.rs | |||
@@ -0,0 +1,113 @@ | |||
1 | use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxKind::*}; | ||
2 | |||
3 | pub fn macro_call_to_tt(call: &ast::MacroCall) -> Option<tt::Subtree> { | ||
4 | let tt = call.token_tree()?; | ||
5 | convert_tt(tt.syntax()) | ||
6 | } | ||
7 | |||
8 | fn convert_tt(tt: &SyntaxNode) -> Option<tt::Subtree> { | ||
9 | let first_child = tt.first_child()?; | ||
10 | let last_child = tt.last_child()?; | ||
11 | let delimiter = match (first_child.kind(), last_child.kind()) { | ||
12 | (L_PAREN, R_PAREN) => tt::Delimiter::Parenthesis, | ||
13 | (L_CURLY, R_CURLY) => tt::Delimiter::Brace, | ||
14 | (L_BRACK, R_BRACK) => tt::Delimiter::Bracket, | ||
15 | _ => return None, | ||
16 | }; | ||
17 | let mut token_trees = Vec::new(); | ||
18 | for child in tt.children().skip(1) { | ||
19 | if child == first_child || child == last_child || child.kind().is_trivia() { | ||
20 | continue; | ||
21 | } | ||
22 | if child.kind().is_punct() { | ||
23 | let mut prev = None; | ||
24 | for char in child.leaf_text().unwrap().chars() { | ||
25 | if let Some(char) = prev { | ||
26 | token_trees.push( | ||
27 | tt::Leaf::from(tt::Punct { | ||
28 | char, | ||
29 | spacing: tt::Spacing::Joint, | ||
30 | }) | ||
31 | .into(), | ||
32 | ); | ||
33 | } | ||
34 | prev = Some(char) | ||
35 | } | ||
36 | if let Some(char) = prev { | ||
37 | token_trees.push( | ||
38 | tt::Leaf::from(tt::Punct { | ||
39 | char, | ||
40 | spacing: tt::Spacing::Alone, | ||
41 | }) | ||
42 | .into(), | ||
43 | ); | ||
44 | } | ||
45 | } else { | ||
46 | let child: tt::TokenTree = if child.kind() == TOKEN_TREE { | ||
47 | convert_tt(child)?.into() | ||
48 | } else if child.kind().is_keyword() || child.kind() == IDENT { | ||
49 | let text = child.leaf_text().unwrap().clone(); | ||
50 | tt::Leaf::from(tt::Ident { text }).into() | ||
51 | } else if child.kind().is_literal() { | ||
52 | tt::Leaf::from(tt::Literal { | ||
53 | text: child.leaf_text().unwrap().clone(), | ||
54 | }) | ||
55 | .into() | ||
56 | } else { | ||
57 | return None; | ||
58 | }; | ||
59 | token_trees.push(child) | ||
60 | } | ||
61 | } | ||
62 | |||
63 | let res = tt::Subtree { | ||
64 | delimiter, | ||
65 | token_trees, | ||
66 | }; | ||
67 | Some(res) | ||
68 | } | ||
69 | |||
70 | #[test] | ||
71 | fn test_convert_tt() { | ||
72 | let macro_definition = r#" | ||
73 | macro_rules! impl_froms { | ||
74 | ($e:ident: $($v:ident),*) => { | ||
75 | $( | ||
76 | impl From<$v> for $e { | ||
77 | fn from(it: $v) -> $e { | ||
78 | $e::$v(it) | ||
79 | } | ||
80 | } | ||
81 | )* | ||
82 | } | ||
83 | } | ||
84 | "#; | ||
85 | |||
86 | let macro_invocation = r#" | ||
87 | impl_froms!(TokenTree: Leaf, Subtree); | ||
88 | "#; | ||
89 | |||
90 | let source_file = ast::SourceFile::parse(macro_definition); | ||
91 | let macro_definition = source_file | ||
92 | .syntax() | ||
93 | .descendants() | ||
94 | .find_map(ast::MacroCall::cast) | ||
95 | .unwrap(); | ||
96 | |||
97 | let source_file = ast::SourceFile::parse(macro_invocation); | ||
98 | let macro_invocation = source_file | ||
99 | .syntax() | ||
100 | .descendants() | ||
101 | .find_map(ast::MacroCall::cast) | ||
102 | .unwrap(); | ||
103 | |||
104 | let definition_tt = macro_call_to_tt(macro_definition).unwrap(); | ||
105 | let invocation_tt = macro_call_to_tt(macro_invocation).unwrap(); | ||
106 | let mbe = crate::parse(&definition_tt).unwrap(); | ||
107 | let expansion = crate::exapnd(&mbe, &invocation_tt).unwrap(); | ||
108 | assert_eq!( | ||
109 | expansion.to_string(), | ||
110 | "{(impl From < Leaf > for TokenTree {fn from (it : Leaf) -> TokenTree {TokenTree :: Leaf (it)}}) \ | ||
111 | (impl From < Subtree > for TokenTree {fn from (it : Subtree) -> TokenTree {TokenTree :: Subtree (it)}})}" | ||
112 | ) | ||
113 | } | ||