diff options
author | Benjamin Coenen <[email protected]> | 2020-04-11 21:54:22 +0100 |
---|---|---|
committer | Benjamin Coenen <[email protected]> | 2020-04-11 22:45:09 +0100 |
commit | 93bfc2d05d36a47dc05a1799210327473d702dbc (patch) | |
tree | dee25e78b24b5d1b23d73ae1009bddbd060927cf /xtask/src/codegen | |
parent | d42346fed61f706d68fe888631a41ea5f2752d7f (diff) | |
parent | fd06fe7b13045185ab4e630b0044aa9d8bbcdf8a (diff) |
Improve autocompletion by looking on the type and name
Signed-off-by: Benjamin Coenen <[email protected]>
Diffstat (limited to 'xtask/src/codegen')
-rw-r--r-- | xtask/src/codegen/gen_syntax.rs | 269 |
1 files changed, 105 insertions, 164 deletions
diff --git a/xtask/src/codegen/gen_syntax.rs b/xtask/src/codegen/gen_syntax.rs index 6657c9fc5..ec1f6ad8a 100644 --- a/xtask/src/codegen/gen_syntax.rs +++ b/xtask/src/codegen/gen_syntax.rs | |||
@@ -3,16 +3,13 @@ | |||
3 | //! Specifically, it generates the `SyntaxKind` enum and a number of newtype | 3 | //! Specifically, it generates the `SyntaxKind` enum and a number of newtype |
4 | //! wrappers around `SyntaxNode` which implement `ra_syntax::AstNode`. | 4 | //! wrappers around `SyntaxNode` which implement `ra_syntax::AstNode`. |
5 | 5 | ||
6 | use std::{ | 6 | use std::collections::HashSet; |
7 | borrow::Cow, | ||
8 | collections::{BTreeSet, HashSet}, | ||
9 | }; | ||
10 | 7 | ||
11 | use proc_macro2::{Punct, Spacing}; | 8 | use proc_macro2::{Punct, Spacing}; |
12 | use quote::{format_ident, quote}; | 9 | use quote::{format_ident, quote}; |
13 | 10 | ||
14 | use crate::{ | 11 | use crate::{ |
15 | ast_src::{AstSrc, FieldSrc, KindsSrc, AST_SRC, KINDS_SRC}, | 12 | ast_src::{AstSrc, Field, FieldSrc, KindsSrc, AST_SRC, KINDS_SRC}, |
16 | codegen::{self, update, Mode}, | 13 | codegen::{self, update, Mode}, |
17 | project_root, Result, | 14 | project_root, Result, |
18 | }; | 15 | }; |
@@ -22,58 +19,31 @@ pub fn generate_syntax(mode: Mode) -> Result<()> { | |||
22 | let syntax_kinds = generate_syntax_kinds(KINDS_SRC)?; | 19 | let syntax_kinds = generate_syntax_kinds(KINDS_SRC)?; |
23 | update(syntax_kinds_file.as_path(), &syntax_kinds, mode)?; | 20 | update(syntax_kinds_file.as_path(), &syntax_kinds, mode)?; |
24 | 21 | ||
22 | let ast_tokens_file = project_root().join(codegen::AST_TOKENS); | ||
23 | let contents = generate_tokens(AST_SRC)?; | ||
24 | update(ast_tokens_file.as_path(), &contents, mode)?; | ||
25 | |||
25 | let ast_nodes_file = project_root().join(codegen::AST_NODES); | 26 | let ast_nodes_file = project_root().join(codegen::AST_NODES); |
26 | let contents = generate_nodes(KINDS_SRC, AST_SRC)?; | 27 | let contents = generate_nodes(KINDS_SRC, AST_SRC)?; |
27 | update(ast_nodes_file.as_path(), &contents, mode)?; | 28 | update(ast_nodes_file.as_path(), &contents, mode)?; |
28 | 29 | ||
29 | let ast_tokens_file = project_root().join(codegen::AST_TOKENS); | ||
30 | let contents = generate_tokens(KINDS_SRC, AST_SRC)?; | ||
31 | update(ast_tokens_file.as_path(), &contents, mode)?; | ||
32 | |||
33 | Ok(()) | 30 | Ok(()) |
34 | } | 31 | } |
35 | 32 | ||
36 | #[derive(Debug, Default, Clone)] | 33 | fn generate_tokens(grammar: AstSrc<'_>) -> Result<String> { |
37 | struct ElementKinds { | 34 | let tokens = grammar.tokens.iter().map(|token| { |
38 | kinds: BTreeSet<proc_macro2::Ident>, | 35 | let name = format_ident!("{}", token); |
39 | has_nodes: bool, | 36 | let kind = format_ident!("{}", to_upper_snake_case(token)); |
40 | has_tokens: bool, | ||
41 | } | ||
42 | |||
43 | fn generate_tokens(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | ||
44 | let all_token_kinds: Vec<_> = kinds | ||
45 | .punct | ||
46 | .into_iter() | ||
47 | .map(|(_, kind)| kind) | ||
48 | .copied() | ||
49 | .map(|x| x.into()) | ||
50 | .chain( | ||
51 | kinds | ||
52 | .keywords | ||
53 | .into_iter() | ||
54 | .chain(kinds.contextual_keywords.into_iter()) | ||
55 | .map(|name| Cow::Owned(format!("{}_KW", to_upper_snake_case(&name)))), | ||
56 | ) | ||
57 | .chain(kinds.literals.into_iter().copied().map(|x| x.into())) | ||
58 | .chain(kinds.tokens.into_iter().copied().map(|x| x.into())) | ||
59 | .collect(); | ||
60 | |||
61 | let tokens = all_token_kinds.iter().map(|kind_str| { | ||
62 | let kind_str = &**kind_str; | ||
63 | let kind = format_ident!("{}", kind_str); | ||
64 | let name = format_ident!("{}", to_pascal_case(kind_str)); | ||
65 | quote! { | 37 | quote! { |
66 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 38 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
67 | pub struct #name { | 39 | pub struct #name { |
68 | pub(crate) syntax: SyntaxToken, | 40 | pub(crate) syntax: SyntaxToken, |
69 | } | 41 | } |
70 | |||
71 | impl std::fmt::Display for #name { | 42 | impl std::fmt::Display for #name { |
72 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | 43 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
73 | std::fmt::Display::fmt(&self.syntax, f) | 44 | std::fmt::Display::fmt(&self.syntax, f) |
74 | } | 45 | } |
75 | } | 46 | } |
76 | |||
77 | impl AstToken for #name { | 47 | impl AstToken for #name { |
78 | fn can_cast(kind: SyntaxKind) -> bool { kind == #kind } | 48 | fn can_cast(kind: SyntaxKind) -> bool { kind == #kind } |
79 | fn cast(syntax: SyntaxToken) -> Option<Self> { | 49 | fn cast(syntax: SyntaxToken) -> Option<Self> { |
@@ -84,99 +54,15 @@ fn generate_tokens(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
84 | } | 54 | } |
85 | }); | 55 | }); |
86 | 56 | ||
87 | let enums = grammar.token_enums.iter().map(|en| { | 57 | let pretty = crate::reformat(quote! { |
88 | let variants = en.variants.iter().map(|var| format_ident!("{}", var)).collect::<Vec<_>>(); | 58 | use crate::{SyntaxKind::{self, *}, SyntaxToken, ast::AstToken}; |
89 | let name = format_ident!("{}", en.name); | ||
90 | let kinds = variants | ||
91 | .iter() | ||
92 | .map(|name| format_ident!("{}", to_upper_snake_case(&name.to_string()))) | ||
93 | .collect::<Vec<_>>(); | ||
94 | assert!(en.traits.is_empty()); | ||
95 | |||
96 | quote! { | ||
97 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
98 | pub enum #name { | ||
99 | #(#variants(#variants),)* | ||
100 | } | ||
101 | |||
102 | #( | ||
103 | impl From<#variants> for #name { | ||
104 | fn from(node: #variants) -> #name { | ||
105 | #name::#variants(node) | ||
106 | } | ||
107 | } | ||
108 | )* | ||
109 | |||
110 | impl std::fmt::Display for #name { | ||
111 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
112 | std::fmt::Display::fmt(self.syntax(), f) | ||
113 | } | ||
114 | } | ||
115 | |||
116 | impl AstToken for #name { | ||
117 | fn can_cast(kind: SyntaxKind) -> bool { | ||
118 | match kind { | ||
119 | #(#kinds)|* => true, | ||
120 | _ => false, | ||
121 | } | ||
122 | } | ||
123 | fn cast(syntax: SyntaxToken) -> Option<Self> { | ||
124 | let res = match syntax.kind() { | ||
125 | #( | ||
126 | #kinds => #name::#variants(#variants { syntax }), | ||
127 | )* | ||
128 | _ => return None, | ||
129 | }; | ||
130 | Some(res) | ||
131 | } | ||
132 | fn syntax(&self) -> &SyntaxToken { | ||
133 | match self { | ||
134 | #( | ||
135 | #name::#variants(it) => &it.syntax, | ||
136 | )* | ||
137 | } | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | }); | ||
142 | |||
143 | crate::reformat(quote! { | ||
144 | use crate::{SyntaxToken, SyntaxKind::{self, *}, ast::AstToken}; | ||
145 | |||
146 | #(#tokens)* | 59 | #(#tokens)* |
147 | #(#enums)* | 60 | })? |
148 | }) | 61 | .replace("#[derive", "\n#[derive"); |
62 | Ok(pretty) | ||
149 | } | 63 | } |
150 | 64 | ||
151 | fn generate_nodes(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | 65 | fn generate_nodes(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { |
152 | let all_token_kinds: Vec<_> = kinds | ||
153 | .punct | ||
154 | .into_iter() | ||
155 | .map(|(_, kind)| kind) | ||
156 | .copied() | ||
157 | .map(|x| x.into()) | ||
158 | .chain( | ||
159 | kinds | ||
160 | .keywords | ||
161 | .into_iter() | ||
162 | .chain(kinds.contextual_keywords.into_iter()) | ||
163 | .map(|name| Cow::Owned(format!("{}_KW", to_upper_snake_case(&name)))), | ||
164 | ) | ||
165 | .chain(kinds.literals.into_iter().copied().map(|x| x.into())) | ||
166 | .chain(kinds.tokens.into_iter().copied().map(|x| x.into())) | ||
167 | .collect(); | ||
168 | |||
169 | let mut token_kinds = HashSet::new(); | ||
170 | for kind in &all_token_kinds { | ||
171 | let kind = &**kind; | ||
172 | let name = to_pascal_case(kind); | ||
173 | token_kinds.insert(name); | ||
174 | } | ||
175 | |||
176 | for en in grammar.token_enums { | ||
177 | token_kinds.insert(en.name.to_string()); | ||
178 | } | ||
179 | |||
180 | let nodes = grammar.nodes.iter().map(|node| { | 66 | let nodes = grammar.nodes.iter().map(|node| { |
181 | let name = format_ident!("{}", node.name); | 67 | let name = format_ident!("{}", node.name); |
182 | let kind = format_ident!("{}", to_upper_snake_case(&name.to_string())); | 68 | let kind = format_ident!("{}", to_upper_snake_case(&name.to_string())); |
@@ -185,39 +71,27 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
185 | quote!(impl ast::#trait_name for #name {}) | 71 | quote!(impl ast::#trait_name for #name {}) |
186 | }); | 72 | }); |
187 | 73 | ||
188 | let methods = node.fields.iter().map(|(name, field)| { | 74 | let methods = node.fields.iter().map(|field| { |
189 | let method_name = match field { | 75 | let method_name = field.method_name(); |
190 | FieldSrc::Shorthand => format_ident!("{}", to_lower_snake_case(&name)), | 76 | let ty = field.ty(); |
191 | _ => format_ident!("{}", name), | ||
192 | }; | ||
193 | let ty = match field { | ||
194 | FieldSrc::Optional(ty) | FieldSrc::Many(ty) => ty, | ||
195 | FieldSrc::Shorthand => name, | ||
196 | }; | ||
197 | 77 | ||
198 | let ty = format_ident!("{}", ty); | 78 | if field.is_many() { |
199 | 79 | quote! { | |
200 | match field { | 80 | pub fn #method_name(&self) -> AstChildren<#ty> { |
201 | FieldSrc::Many(_) => { | 81 | support::children(&self.syntax) |
202 | quote! { | ||
203 | pub fn #method_name(&self) -> AstChildren<#ty> { | ||
204 | support::children(&self.syntax) | ||
205 | } | ||
206 | } | 82 | } |
207 | } | 83 | } |
208 | FieldSrc::Optional(_) | FieldSrc::Shorthand => { | 84 | } else { |
209 | let is_token = token_kinds.contains(&ty.to_string()); | 85 | if let Some(token_kind) = field.token_kind() { |
210 | if is_token { | 86 | quote! { |
211 | quote! { | 87 | pub fn #method_name(&self) -> Option<#ty> { |
212 | pub fn #method_name(&self) -> Option<#ty> { | 88 | support::token(&self.syntax, #token_kind) |
213 | support::token(&self.syntax) | ||
214 | } | ||
215 | } | 89 | } |
216 | } else { | 90 | } |
217 | quote! { | 91 | } else { |
218 | pub fn #method_name(&self) -> Option<#ty> { | 92 | quote! { |
219 | support::child(&self.syntax) | 93 | pub fn #method_name(&self) -> Option<#ty> { |
220 | } | 94 | support::child(&self.syntax) |
221 | } | 95 | } |
222 | } | 96 | } |
223 | } | 97 | } |
@@ -331,18 +205,18 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { | |||
331 | 205 | ||
332 | let ast = quote! { | 206 | let ast = quote! { |
333 | use crate::{ | 207 | use crate::{ |
334 | SyntaxNode, SyntaxKind::{self, *}, | 208 | SyntaxNode, SyntaxToken, SyntaxKind::{self, *}, |
335 | ast::{self, AstNode, AstChildren, support}, | 209 | ast::{self, AstNode, AstChildren, support}, |
210 | T, | ||
336 | }; | 211 | }; |
337 | 212 | ||
338 | use super::tokens::*; | ||
339 | |||
340 | #(#nodes)* | 213 | #(#nodes)* |
341 | #(#enums)* | 214 | #(#enums)* |
342 | #(#displays)* | 215 | #(#displays)* |
343 | }; | 216 | }; |
344 | 217 | ||
345 | let pretty = crate::reformat(ast)?; | 218 | let ast = ast.to_string().replace("T ! [ ", "T![").replace(" ] )", "])"); |
219 | let pretty = crate::reformat(ast)?.replace("#[derive", "\n#[derive"); | ||
346 | Ok(pretty) | 220 | Ok(pretty) |
347 | } | 221 | } |
348 | 222 | ||
@@ -450,8 +324,10 @@ fn generate_syntax_kinds(grammar: KindsSrc<'_>) -> Result<String> { | |||
450 | 324 | ||
451 | #[macro_export] | 325 | #[macro_export] |
452 | macro_rules! T { | 326 | macro_rules! T { |
453 | #((#punctuation_values) => { $crate::SyntaxKind::#punctuation };)* | 327 | #([#punctuation_values] => { $crate::SyntaxKind::#punctuation };)* |
454 | #((#all_keywords_idents) => { $crate::SyntaxKind::#all_keywords };)* | 328 | #([#all_keywords_idents] => { $crate::SyntaxKind::#all_keywords };)* |
329 | [lifetime] => { $crate::SyntaxKind::LIFETIME }; | ||
330 | [ident] => { $crate::SyntaxKind::IDENT }; | ||
455 | } | 331 | } |
456 | }; | 332 | }; |
457 | 333 | ||
@@ -501,3 +377,68 @@ fn to_pascal_case(s: &str) -> String { | |||
501 | } | 377 | } |
502 | buf | 378 | buf |
503 | } | 379 | } |
380 | |||
381 | impl Field<'_> { | ||
382 | fn is_many(&self) -> bool { | ||
383 | match self { | ||
384 | Field::Node { src: FieldSrc::Many(_), .. } => true, | ||
385 | _ => false, | ||
386 | } | ||
387 | } | ||
388 | fn token_kind(&self) -> Option<proc_macro2::TokenStream> { | ||
389 | let res = match self { | ||
390 | Field::Token(token) => { | ||
391 | let token: proc_macro2::TokenStream = token.parse().unwrap(); | ||
392 | quote! { T![#token] } | ||
393 | } | ||
394 | _ => return None, | ||
395 | }; | ||
396 | Some(res) | ||
397 | } | ||
398 | fn method_name(&self) -> proc_macro2::Ident { | ||
399 | match self { | ||
400 | Field::Token(name) => { | ||
401 | let name = match *name { | ||
402 | ";" => "semicolon", | ||
403 | "->" => "thin_arrow", | ||
404 | "'{'" => "l_curly", | ||
405 | "'}'" => "r_curly", | ||
406 | "'('" => "l_paren", | ||
407 | "')'" => "r_paren", | ||
408 | "'['" => "l_brack", | ||
409 | "']'" => "r_brack", | ||
410 | "<" => "l_angle", | ||
411 | ">" => "r_angle", | ||
412 | "=" => "eq", | ||
413 | "!" => "excl", | ||
414 | "*" => "star", | ||
415 | "&" => "amp", | ||
416 | "_" => "underscore", | ||
417 | "." => "dot", | ||
418 | ".." => "dotdot", | ||
419 | "..." => "dotdotdot", | ||
420 | "=>" => "fat_arrow", | ||
421 | "@" => "at", | ||
422 | ":" => "colon", | ||
423 | "::" => "coloncolon", | ||
424 | "#" => "pound", | ||
425 | _ => name, | ||
426 | }; | ||
427 | format_ident!("{}_token", name) | ||
428 | } | ||
429 | Field::Node { name, src } => match src { | ||
430 | FieldSrc::Shorthand => format_ident!("{}", to_lower_snake_case(name)), | ||
431 | _ => format_ident!("{}", name), | ||
432 | }, | ||
433 | } | ||
434 | } | ||
435 | fn ty(&self) -> proc_macro2::Ident { | ||
436 | match self { | ||
437 | Field::Token(_) => format_ident!("SyntaxToken"), | ||
438 | Field::Node { name, src } => match src { | ||
439 | FieldSrc::Optional(ty) | FieldSrc::Many(ty) => format_ident!("{}", ty), | ||
440 | FieldSrc::Shorthand => format_ident!("{}", name), | ||
441 | }, | ||
442 | } | ||
443 | } | ||
444 | } | ||