diff options
Diffstat (limited to 'crates/ra_mbe')
-rw-r--r-- | crates/ra_mbe/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_mbe/src/lib.rs | 48 | ||||
-rw-r--r-- | crates/ra_mbe/src/syntax_bridge.rs | 203 |
3 files changed, 228 insertions, 25 deletions
diff --git a/crates/ra_mbe/Cargo.toml b/crates/ra_mbe/Cargo.toml index 6e785f570..1d0c2a340 100644 --- a/crates/ra_mbe/Cargo.toml +++ b/crates/ra_mbe/Cargo.toml | |||
@@ -8,5 +8,5 @@ authors = ["rust-analyzer developers"] | |||
8 | ra_syntax = { path = "../ra_syntax" } | 8 | ra_syntax = { path = "../ra_syntax" } |
9 | ra_parser = { path = "../ra_parser" } | 9 | ra_parser = { path = "../ra_parser" } |
10 | tt = { path = "../ra_tt", package = "ra_tt" } | 10 | tt = { path = "../ra_tt", package = "ra_tt" } |
11 | 11 | itertools = "0.8.0" | |
12 | rustc-hash = "1.0.0" | 12 | rustc-hash = "1.0.0" |
diff --git a/crates/ra_mbe/src/lib.rs b/crates/ra_mbe/src/lib.rs index 93246f54a..4203929d4 100644 --- a/crates/ra_mbe/src/lib.rs +++ b/crates/ra_mbe/src/lib.rs | |||
@@ -167,7 +167,7 @@ impl_froms!(TokenTree: Leaf, Subtree); | |||
167 | ) | 167 | ) |
168 | } | 168 | } |
169 | 169 | ||
170 | fn create_rules(macro_definition: &str) -> MacroRules { | 170 | pub(crate) fn create_rules(macro_definition: &str) -> MacroRules { |
171 | let source_file = ast::SourceFile::parse(macro_definition); | 171 | let source_file = ast::SourceFile::parse(macro_definition); |
172 | let macro_definition = | 172 | let macro_definition = |
173 | source_file.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); | 173 | source_file.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); |
@@ -176,7 +176,7 @@ impl_froms!(TokenTree: Leaf, Subtree); | |||
176 | crate::MacroRules::parse(&definition_tt).unwrap() | 176 | crate::MacroRules::parse(&definition_tt).unwrap() |
177 | } | 177 | } |
178 | 178 | ||
179 | fn expand(rules: &MacroRules, invocation: &str) -> tt::Subtree { | 179 | pub(crate) fn expand(rules: &MacroRules, invocation: &str) -> tt::Subtree { |
180 | let source_file = ast::SourceFile::parse(invocation); | 180 | let source_file = ast::SourceFile::parse(invocation); |
181 | let macro_invocation = | 181 | let macro_invocation = |
182 | source_file.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); | 182 | source_file.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); |
@@ -186,7 +186,7 @@ impl_froms!(TokenTree: Leaf, Subtree); | |||
186 | rules.expand(&invocation_tt).unwrap() | 186 | rules.expand(&invocation_tt).unwrap() |
187 | } | 187 | } |
188 | 188 | ||
189 | fn assert_expansion(rules: &MacroRules, invocation: &str, expansion: &str) { | 189 | pub(crate) fn assert_expansion(rules: &MacroRules, invocation: &str, expansion: &str) { |
190 | let expanded = expand(rules, invocation); | 190 | let expanded = expand(rules, invocation); |
191 | assert_eq!(expanded.to_string(), expansion); | 191 | assert_eq!(expanded.to_string(), expansion); |
192 | } | 192 | } |
@@ -337,4 +337,46 @@ SOURCE_FILE@[0; 40) | |||
337 | ); | 337 | ); |
338 | } | 338 | } |
339 | 339 | ||
340 | #[test] | ||
341 | fn expand_literals_to_token_tree() { | ||
342 | fn to_subtree(tt: &tt::TokenTree) -> &tt::Subtree { | ||
343 | if let tt::TokenTree::Subtree(subtree) = tt { | ||
344 | return &subtree; | ||
345 | } | ||
346 | unreachable!("It is not a subtree"); | ||
347 | } | ||
348 | |||
349 | fn to_literal(tt: &tt::TokenTree) -> &tt::Literal { | ||
350 | if let tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) = tt { | ||
351 | return lit; | ||
352 | } | ||
353 | unreachable!("It is not a literal"); | ||
354 | } | ||
355 | |||
356 | let rules = create_rules( | ||
357 | r#" | ||
358 | macro_rules! literals { | ||
359 | ($i:ident) => { | ||
360 | { | ||
361 | let a = 'c'; | ||
362 | let c = 1000; | ||
363 | let f = 12E+99_f64; | ||
364 | let s = "rust1"; | ||
365 | } | ||
366 | } | ||
367 | } | ||
368 | "#, | ||
369 | ); | ||
370 | let expansion = expand(&rules, "literals!(foo)"); | ||
371 | let stm_tokens = &to_subtree(&expansion.token_trees[0]).token_trees; | ||
372 | |||
373 | // [let] [a] [=] ['c'] [;] | ||
374 | assert_eq!(to_literal(&stm_tokens[3]).text, "'c'"); | ||
375 | // [let] [c] [=] [1000] [;] | ||
376 | assert_eq!(to_literal(&stm_tokens[5 + 3]).text, "1000"); | ||
377 | // [let] [f] [=] [12E+99_f64] [;] | ||
378 | assert_eq!(to_literal(&stm_tokens[10 + 3]).text, "12E+99_f64"); | ||
379 | // [let] [s] [=] ["rust1"] [;] | ||
380 | assert_eq!(to_literal(&stm_tokens[15 + 3]).text, "\"rust1\""); | ||
381 | } | ||
340 | } | 382 | } |
diff --git a/crates/ra_mbe/src/syntax_bridge.rs b/crates/ra_mbe/src/syntax_bridge.rs index 05f9817da..139a0fd33 100644 --- a/crates/ra_mbe/src/syntax_bridge.rs +++ b/crates/ra_mbe/src/syntax_bridge.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | use ra_parser::{TokenSource, TreeSink, ParseError}; | 1 | use ra_parser::{TokenSource, TreeSink, ParseError}; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | AstNode, SyntaxNode, TextRange, SyntaxKind, SmolStr, SyntaxTreeBuilder, TreeArc, SyntaxElement, | 3 | AstNode, SyntaxNode, TextRange, SyntaxKind, SmolStr, SyntaxTreeBuilder, TreeArc, SyntaxElement, |
4 | ast, SyntaxKind::*, TextUnit | 4 | ast, SyntaxKind::*, TextUnit, classify_literal |
5 | }; | 5 | }; |
6 | 6 | ||
7 | /// Maps `tt::TokenId` to the relative range of the original token. | 7 | /// Maps `tt::TokenId` to the relative range of the original token. |
@@ -103,16 +103,63 @@ fn convert_tt( | |||
103 | Some(res) | 103 | Some(res) |
104 | } | 104 | } |
105 | 105 | ||
106 | #[derive(Debug)] | ||
106 | struct TtTokenSource { | 107 | struct TtTokenSource { |
107 | tokens: Vec<TtToken>, | 108 | tokens: Vec<TtToken>, |
108 | } | 109 | } |
109 | 110 | ||
111 | #[derive(Debug)] | ||
110 | struct TtToken { | 112 | struct TtToken { |
111 | kind: SyntaxKind, | 113 | kind: SyntaxKind, |
112 | is_joint_to_next: bool, | 114 | is_joint_to_next: bool, |
113 | text: SmolStr, | 115 | text: SmolStr, |
114 | } | 116 | } |
115 | 117 | ||
118 | // Some helper functions | ||
119 | fn to_punct(tt: &tt::TokenTree) -> Option<&tt::Punct> { | ||
120 | if let tt::TokenTree::Leaf(tt::Leaf::Punct(pp)) = tt { | ||
121 | return Some(pp); | ||
122 | } | ||
123 | None | ||
124 | } | ||
125 | |||
126 | struct TokenPeek<'a, I> | ||
127 | where | ||
128 | I: Iterator<Item = &'a tt::TokenTree>, | ||
129 | { | ||
130 | iter: itertools::MultiPeek<I>, | ||
131 | } | ||
132 | |||
133 | impl<'a, I> TokenPeek<'a, I> | ||
134 | where | ||
135 | I: Iterator<Item = &'a tt::TokenTree>, | ||
136 | { | ||
137 | fn next(&mut self) -> Option<&tt::TokenTree> { | ||
138 | self.iter.next() | ||
139 | } | ||
140 | |||
141 | fn current_punct2(&mut self, p: &tt::Punct) -> Option<((char, char), bool)> { | ||
142 | if p.spacing != tt::Spacing::Joint { | ||
143 | return None; | ||
144 | } | ||
145 | |||
146 | self.iter.reset_peek(); | ||
147 | let p1 = to_punct(self.iter.peek()?)?; | ||
148 | Some(((p.char, p1.char), p1.spacing == tt::Spacing::Joint)) | ||
149 | } | ||
150 | |||
151 | fn current_punct3(&mut self, p: &tt::Punct) -> Option<((char, char, char), bool)> { | ||
152 | self.current_punct2(p).and_then(|((p0, p1), last_joint)| { | ||
153 | if !last_joint { | ||
154 | None | ||
155 | } else { | ||
156 | let p2 = to_punct(*self.iter.peek()?)?; | ||
157 | Some(((p0, p1, p2.char), p2.spacing == tt::Spacing::Joint)) | ||
158 | } | ||
159 | }) | ||
160 | } | ||
161 | } | ||
162 | |||
116 | impl TtTokenSource { | 163 | impl TtTokenSource { |
117 | fn new(tt: &tt::Subtree) -> TtTokenSource { | 164 | fn new(tt: &tt::Subtree) -> TtTokenSource { |
118 | let mut res = TtTokenSource { tokens: Vec::new() }; | 165 | let mut res = TtTokenSource { tokens: Vec::new() }; |
@@ -121,38 +168,53 @@ impl TtTokenSource { | |||
121 | } | 168 | } |
122 | fn convert_subtree(&mut self, sub: &tt::Subtree) { | 169 | fn convert_subtree(&mut self, sub: &tt::Subtree) { |
123 | self.push_delim(sub.delimiter, false); | 170 | self.push_delim(sub.delimiter, false); |
124 | sub.token_trees.iter().for_each(|tt| self.convert_tt(tt)); | 171 | let mut peek = TokenPeek { iter: itertools::multipeek(sub.token_trees.iter()) }; |
172 | while let Some(tt) = peek.iter.next() { | ||
173 | self.convert_tt(tt, &mut peek); | ||
174 | } | ||
125 | self.push_delim(sub.delimiter, true) | 175 | self.push_delim(sub.delimiter, true) |
126 | } | 176 | } |
127 | fn convert_tt(&mut self, tt: &tt::TokenTree) { | 177 | |
178 | fn convert_tt<'a, I>(&mut self, tt: &tt::TokenTree, iter: &mut TokenPeek<'a, I>) | ||
179 | where | ||
180 | I: Iterator<Item = &'a tt::TokenTree>, | ||
181 | { | ||
128 | match tt { | 182 | match tt { |
129 | tt::TokenTree::Leaf(token) => self.convert_token(token), | 183 | tt::TokenTree::Leaf(token) => self.convert_token(token, iter), |
130 | tt::TokenTree::Subtree(sub) => self.convert_subtree(sub), | 184 | tt::TokenTree::Subtree(sub) => self.convert_subtree(sub), |
131 | } | 185 | } |
132 | } | 186 | } |
133 | fn convert_token(&mut self, token: &tt::Leaf) { | 187 | |
188 | fn convert_token<'a, I>(&mut self, token: &tt::Leaf, iter: &mut TokenPeek<'a, I>) | ||
189 | where | ||
190 | I: Iterator<Item = &'a tt::TokenTree>, | ||
191 | { | ||
134 | let tok = match token { | 192 | let tok = match token { |
135 | tt::Leaf::Literal(l) => TtToken { | 193 | tt::Leaf::Literal(l) => TtToken { |
136 | kind: SyntaxKind::INT_NUMBER, // FIXME | 194 | kind: classify_literal(&l.text).unwrap().kind, |
137 | is_joint_to_next: false, | 195 | is_joint_to_next: false, |
138 | text: l.text.clone(), | 196 | text: l.text.clone(), |
139 | }, | 197 | }, |
140 | tt::Leaf::Punct(p) => { | 198 | tt::Leaf::Punct(p) => { |
141 | let kind = match p.char { | 199 | if let Some(tt) = Self::convert_multi_char_punct(p, iter) { |
142 | // lexer may produce combpund tokens for these ones | 200 | tt |
143 | '.' => DOT, | 201 | } else { |
144 | ':' => COLON, | 202 | let kind = match p.char { |
145 | '=' => EQ, | 203 | // lexer may produce combpund tokens for these ones |
146 | '!' => EXCL, | 204 | '.' => DOT, |
147 | '-' => MINUS, | 205 | ':' => COLON, |
148 | c => SyntaxKind::from_char(c).unwrap(), | 206 | '=' => EQ, |
149 | }; | 207 | '!' => EXCL, |
150 | let text = { | 208 | '-' => MINUS, |
151 | let mut buf = [0u8; 4]; | 209 | c => SyntaxKind::from_char(c).unwrap(), |
152 | let s: &str = p.char.encode_utf8(&mut buf); | 210 | }; |
153 | SmolStr::new(s) | 211 | let text = { |
154 | }; | 212 | let mut buf = [0u8; 4]; |
155 | TtToken { kind, is_joint_to_next: p.spacing == tt::Spacing::Joint, text } | 213 | let s: &str = p.char.encode_utf8(&mut buf); |
214 | SmolStr::new(s) | ||
215 | }; | ||
216 | TtToken { kind, is_joint_to_next: p.spacing == tt::Spacing::Joint, text } | ||
217 | } | ||
156 | } | 218 | } |
157 | tt::Leaf::Ident(ident) => { | 219 | tt::Leaf::Ident(ident) => { |
158 | let kind = SyntaxKind::from_keyword(ident.text.as_str()).unwrap_or(IDENT); | 220 | let kind = SyntaxKind::from_keyword(ident.text.as_str()).unwrap_or(IDENT); |
@@ -161,6 +223,64 @@ impl TtTokenSource { | |||
161 | }; | 223 | }; |
162 | self.tokens.push(tok) | 224 | self.tokens.push(tok) |
163 | } | 225 | } |
226 | |||
227 | fn convert_multi_char_punct<'a, I>( | ||
228 | p: &tt::Punct, | ||
229 | iter: &mut TokenPeek<'a, I>, | ||
230 | ) -> Option<TtToken> | ||
231 | where | ||
232 | I: Iterator<Item = &'a tt::TokenTree>, | ||
233 | { | ||
234 | if let Some((m, is_joint_to_next)) = iter.current_punct3(p) { | ||
235 | if let Some((kind, text)) = match m { | ||
236 | ('<', '<', '=') => Some((SHLEQ, "<<=")), | ||
237 | ('>', '>', '=') => Some((SHREQ, ">>=")), | ||
238 | ('.', '.', '.') => Some((DOTDOTDOT, "...")), | ||
239 | ('.', '.', '=') => Some((DOTDOTEQ, "..=")), | ||
240 | _ => None, | ||
241 | } { | ||
242 | iter.next(); | ||
243 | iter.next(); | ||
244 | return Some(TtToken { kind, is_joint_to_next, text: text.into() }); | ||
245 | } | ||
246 | } | ||
247 | |||
248 | if let Some((m, is_joint_to_next)) = iter.current_punct2(p) { | ||
249 | if let Some((kind, text)) = match m { | ||
250 | ('<', '<') => Some((SHL, "<<")), | ||
251 | ('>', '>') => Some((SHR, ">>")), | ||
252 | |||
253 | ('|', '|') => Some((PIPEPIPE, "||")), | ||
254 | ('&', '&') => Some((AMPAMP, "&&")), | ||
255 | ('%', '=') => Some((PERCENTEQ, "%=")), | ||
256 | ('*', '=') => Some((STAREQ, "*=")), | ||
257 | ('/', '=') => Some((SLASHEQ, "/=")), | ||
258 | ('^', '=') => Some((CARETEQ, "^=")), | ||
259 | |||
260 | ('&', '=') => Some((AMPEQ, "&=")), | ||
261 | ('|', '=') => Some((PIPEEQ, "|=")), | ||
262 | ('-', '=') => Some((MINUSEQ, "-=")), | ||
263 | ('+', '=') => Some((PLUSEQ, "+=")), | ||
264 | ('>', '=') => Some((GTEQ, ">=")), | ||
265 | ('<', '=') => Some((LTEQ, "<=")), | ||
266 | |||
267 | ('-', '>') => Some((THIN_ARROW, "->")), | ||
268 | ('!', '=') => Some((NEQ, "!=")), | ||
269 | ('=', '>') => Some((FAT_ARROW, "=>")), | ||
270 | ('=', '=') => Some((EQEQ, "==")), | ||
271 | ('.', '.') => Some((DOTDOT, "..")), | ||
272 | (':', ':') => Some((COLONCOLON, "::")), | ||
273 | |||
274 | _ => None, | ||
275 | } { | ||
276 | iter.next(); | ||
277 | return Some(TtToken { kind, is_joint_to_next, text: text.into() }); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | None | ||
282 | } | ||
283 | |||
164 | fn push_delim(&mut self, d: tt::Delimiter, closing: bool) { | 284 | fn push_delim(&mut self, d: tt::Delimiter, closing: bool) { |
165 | let (kinds, texts) = match d { | 285 | let (kinds, texts) = match d { |
166 | tt::Delimiter::Parenthesis => ([L_PAREN, R_PAREN], "()"), | 286 | tt::Delimiter::Parenthesis => ([L_PAREN, R_PAREN], "()"), |
@@ -237,3 +357,44 @@ impl<'a> TreeSink for TtTreeSink<'a> { | |||
237 | self.inner.error(error, self.text_pos) | 357 | self.inner.error(error, self.text_pos) |
238 | } | 358 | } |
239 | } | 359 | } |
360 | |||
361 | #[cfg(test)] | ||
362 | mod tests { | ||
363 | use super::*; | ||
364 | use crate::tests::{expand, create_rules}; | ||
365 | |||
366 | #[test] | ||
367 | fn convert_tt_token_source() { | ||
368 | let rules = create_rules( | ||
369 | r#" | ||
370 | macro_rules! literals { | ||
371 | ($i:ident) => { | ||
372 | { | ||
373 | let a = 'c'; | ||
374 | let c = 1000; | ||
375 | let f = 12E+99_f64; | ||
376 | let s = "rust1"; | ||
377 | } | ||
378 | } | ||
379 | } | ||
380 | "#, | ||
381 | ); | ||
382 | let expansion = expand(&rules, "literals!(foo)"); | ||
383 | let tt_src = TtTokenSource::new(&expansion); | ||
384 | |||
385 | // [{] | ||
386 | // [let] [a] [=] ['c'] [;] | ||
387 | assert_eq!(tt_src.tokens[1 + 3].text, "'c'"); | ||
388 | assert_eq!(tt_src.tokens[1 + 3].kind, CHAR); | ||
389 | // [let] [c] [=] [1000] [;] | ||
390 | assert_eq!(tt_src.tokens[1 + 5 + 3].text, "1000"); | ||
391 | assert_eq!(tt_src.tokens[1 + 5 + 3].kind, INT_NUMBER); | ||
392 | // [let] [f] [=] [12E+99_f64] [;] | ||
393 | assert_eq!(tt_src.tokens[1 + 10 + 3].text, "12E+99_f64"); | ||
394 | assert_eq!(tt_src.tokens[1 + 10 + 3].kind, FLOAT_NUMBER); | ||
395 | |||
396 | // [let] [s] [=] ["rust1"] [;] | ||
397 | assert_eq!(tt_src.tokens[1 + 15 + 3].text, "\"rust1\""); | ||
398 | assert_eq!(tt_src.tokens[1 + 15 + 3].kind, STRING); | ||
399 | } | ||
400 | } | ||