aboutsummaryrefslogtreecommitdiff
path: root/xtask/src/codegen
diff options
context:
space:
mode:
authorBenjamin Coenen <[email protected]>2020-04-11 21:54:22 +0100
committerBenjamin Coenen <[email protected]>2020-04-11 22:45:09 +0100
commit93bfc2d05d36a47dc05a1799210327473d702dbc (patch)
treedee25e78b24b5d1b23d73ae1009bddbd060927cf /xtask/src/codegen
parentd42346fed61f706d68fe888631a41ea5f2752d7f (diff)
parentfd06fe7b13045185ab4e630b0044aa9d8bbcdf8a (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.rs269
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
6use std::{ 6use std::collections::HashSet;
7 borrow::Cow,
8 collections::{BTreeSet, HashSet},
9};
10 7
11use proc_macro2::{Punct, Spacing}; 8use proc_macro2::{Punct, Spacing};
12use quote::{format_ident, quote}; 9use quote::{format_ident, quote};
13 10
14use crate::{ 11use 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)] 33fn generate_tokens(grammar: AstSrc<'_>) -> Result<String> {
37struct 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
43fn 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
151fn generate_nodes(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result<String> { 65fn 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
381impl 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}