diff options
Diffstat (limited to 'crates/ra_tools/src/boilerplate_gen.rs')
-rw-r--r-- | crates/ra_tools/src/boilerplate_gen.rs | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/crates/ra_tools/src/boilerplate_gen.rs b/crates/ra_tools/src/boilerplate_gen.rs new file mode 100644 index 000000000..5547d6969 --- /dev/null +++ b/crates/ra_tools/src/boilerplate_gen.rs | |||
@@ -0,0 +1,336 @@ | |||
1 | use std::{ | ||
2 | collections::BTreeMap, | ||
3 | fs, | ||
4 | io::Write, | ||
5 | path::Path, | ||
6 | process::{Command, Stdio}, | ||
7 | }; | ||
8 | |||
9 | use heck::{ShoutySnakeCase, SnakeCase}; | ||
10 | use proc_macro2::{Punct, Spacing}; | ||
11 | use quote::{format_ident, quote}; | ||
12 | use ron; | ||
13 | use serde::Deserialize; | ||
14 | |||
15 | use crate::{project_root, Mode, Result, AST, GRAMMAR, SYNTAX_KINDS}; | ||
16 | |||
17 | pub fn generate_boilerplate(mode: Mode) -> Result<()> { | ||
18 | let grammar = project_root().join(GRAMMAR); | ||
19 | let grammar: Grammar = { | ||
20 | let text = fs::read_to_string(grammar)?; | ||
21 | ron::de::from_str(&text)? | ||
22 | }; | ||
23 | |||
24 | let _syntax_kinds = project_root().join(SYNTAX_KINDS); | ||
25 | let _ast = project_root().join(AST); | ||
26 | |||
27 | let ast = generate_syntax_kinds(&grammar)?; | ||
28 | println!("{}", ast); | ||
29 | Ok(()) | ||
30 | } | ||
31 | |||
32 | fn generate_ast(grammar: &Grammar) -> Result<String> { | ||
33 | let nodes = grammar.ast.iter().map(|(name, ast_node)| { | ||
34 | let variants = | ||
35 | ast_node.variants.iter().map(|var| format_ident!("{}", var)).collect::<Vec<_>>(); | ||
36 | let name = format_ident!("{}", name); | ||
37 | |||
38 | let kinds = if variants.is_empty() { vec![name.clone()] } else { variants.clone() } | ||
39 | .into_iter() | ||
40 | .map(|name| format_ident!("{}", name.to_string().to_shouty_snake_case())) | ||
41 | .collect::<Vec<_>>(); | ||
42 | |||
43 | let variants = if variants.is_empty() { | ||
44 | None | ||
45 | } else { | ||
46 | let kind_enum = format_ident!("{}Kind", name); | ||
47 | Some(quote!( | ||
48 | pub enum #kind_enum { | ||
49 | #(#variants(#variants),)* | ||
50 | } | ||
51 | |||
52 | #( | ||
53 | impl From<#variants> for #name { | ||
54 | fn from(node: #variants) -> #name { | ||
55 | #name { syntax: node.syntax } | ||
56 | } | ||
57 | } | ||
58 | )* | ||
59 | |||
60 | impl #name { | ||
61 | pub fn kind(&self) -> #kind_enum { | ||
62 | let syntax = self.syntax.clone(); | ||
63 | match syntax.kind() { | ||
64 | #( | ||
65 | #kinds => | ||
66 | #kind_enum::#variants(#variants { syntax }), | ||
67 | )* | ||
68 | _ => unreachable!(), | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | )) | ||
73 | }; | ||
74 | |||
75 | let traits = ast_node.traits.iter().map(|trait_name| { | ||
76 | let trait_name = format_ident!("{}", trait_name); | ||
77 | quote!(impl ast::#trait_name for #name {}) | ||
78 | }); | ||
79 | |||
80 | let collections = ast_node.collections.iter().map(|(name, kind)| { | ||
81 | let method_name = format_ident!("{}", name); | ||
82 | let kind = format_ident!("{}", kind); | ||
83 | quote! { | ||
84 | pub fn #method_name(&self) -> AstChildren<#kind> { | ||
85 | AstChildren::new(&self.syntax) | ||
86 | } | ||
87 | } | ||
88 | }); | ||
89 | |||
90 | let options = ast_node.options.iter().map(|attr| { | ||
91 | let method_name = match attr { | ||
92 | Attr::Type(t) => format_ident!("{}", t.to_snake_case()), | ||
93 | Attr::NameType(n, _) => format_ident!("{}", n), | ||
94 | }; | ||
95 | let ty = match attr { | ||
96 | Attr::Type(t) | Attr::NameType(_, t) => format_ident!("{}", t), | ||
97 | }; | ||
98 | quote! { | ||
99 | pub fn #method_name(&self) -> Option<#ty> { | ||
100 | AstChildren::new(&self.syntax).next() | ||
101 | } | ||
102 | } | ||
103 | }); | ||
104 | |||
105 | quote! { | ||
106 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
107 | pub struct #name { | ||
108 | pub(crate) syntax: SyntaxNode, | ||
109 | } | ||
110 | |||
111 | impl AstNode for #name { | ||
112 | fn can_cast(kind: SyntaxKind) -> bool { | ||
113 | match kind { | ||
114 | #(#kinds)|* => true, | ||
115 | _ => false, | ||
116 | } | ||
117 | } | ||
118 | fn cast(syntax: SyntaxNode) -> Option<Self> { | ||
119 | if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } | ||
120 | } | ||
121 | fn syntax(&self) -> &SyntaxNode { &self.syntax } | ||
122 | } | ||
123 | |||
124 | #variants | ||
125 | |||
126 | #(#traits)* | ||
127 | |||
128 | impl #name { | ||
129 | #(#collections)* | ||
130 | #(#options)* | ||
131 | } | ||
132 | } | ||
133 | }); | ||
134 | |||
135 | let ast = quote! { | ||
136 | use crate::{ | ||
137 | SyntaxNode, SyntaxKind::{self, *}, | ||
138 | ast::{self, AstNode, AstChildren}, | ||
139 | }; | ||
140 | |||
141 | #(#nodes)* | ||
142 | }; | ||
143 | |||
144 | let pretty = reformat(ast)?; | ||
145 | Ok(pretty) | ||
146 | } | ||
147 | |||
148 | fn generate_syntax_kinds(grammar: &Grammar) -> Result<String> { | ||
149 | let single_byte_tokens_values = | ||
150 | grammar.single_byte_tokens.iter().map(|(token, _name)| token.chars().next().unwrap()); | ||
151 | let single_byte_tokens = grammar | ||
152 | .single_byte_tokens | ||
153 | .iter() | ||
154 | .map(|(_token, name)| format_ident!("{}", name)) | ||
155 | .collect::<Vec<_>>(); | ||
156 | |||
157 | let punctuation_values = | ||
158 | grammar.single_byte_tokens.iter().chain(grammar.multi_byte_tokens.iter()).map( | ||
159 | |(token, _name)| { | ||
160 | if "{}[]()".contains(token) { | ||
161 | let c = token.chars().next().unwrap(); | ||
162 | quote! { #c } | ||
163 | } else { | ||
164 | let cs = token.chars().map(|c| Punct::new(c, Spacing::Joint)); | ||
165 | quote! { #(#cs)* } | ||
166 | } | ||
167 | }, | ||
168 | ); | ||
169 | let punctuation = single_byte_tokens | ||
170 | .clone() | ||
171 | .into_iter() | ||
172 | .chain(grammar.multi_byte_tokens.iter().map(|(_token, name)| format_ident!("{}", name))) | ||
173 | .collect::<Vec<_>>(); | ||
174 | |||
175 | let keywords_values = | ||
176 | grammar.keywords.iter().chain(grammar.contextual_keywords.iter()).collect::<Vec<_>>(); | ||
177 | let keywords_idents = keywords_values.iter().map(|kw| format_ident!("{}", kw)); | ||
178 | let keywords = keywords_values | ||
179 | .iter() | ||
180 | .map(|name| format_ident!("{}_KW", name.to_shouty_snake_case())) | ||
181 | .collect::<Vec<_>>(); | ||
182 | |||
183 | let literals = | ||
184 | grammar.literals.iter().map(|name| format_ident!("{}", name)).collect::<Vec<_>>(); | ||
185 | |||
186 | let tokens = grammar.tokens.iter().map(|name| format_ident!("{}", name)).collect::<Vec<_>>(); | ||
187 | |||
188 | let nodes = grammar.nodes.iter().map(|name| format_ident!("{}", name)).collect::<Vec<_>>(); | ||
189 | |||
190 | let ast = quote! { | ||
191 | #![allow(bad_style, missing_docs, unreachable_pub)] | ||
192 | use super::SyntaxInfo; | ||
193 | |||
194 | /// The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT_DEF`. | ||
195 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
196 | #[repr(u16)] | ||
197 | pub enum SyntaxKind { | ||
198 | // Technical SyntaxKinds: they appear temporally during parsing, | ||
199 | // but never end up in the final tree | ||
200 | #[doc(hidden)] | ||
201 | TOMBSTONE, | ||
202 | #[doc(hidden)] | ||
203 | EOF, | ||
204 | #(#punctuation,)* | ||
205 | #(#keywords,)* | ||
206 | #(#literals,)* | ||
207 | #(#tokens,)* | ||
208 | #(#nodes,)* | ||
209 | |||
210 | // Technical kind so that we can cast from u16 safely | ||
211 | #[doc(hidden)] | ||
212 | __LAST, | ||
213 | } | ||
214 | use self::SyntaxKind::*; | ||
215 | |||
216 | impl From<u16> for SyntaxKind { | ||
217 | fn from(d: u16) -> SyntaxKind { | ||
218 | assert!(d <= (__LAST as u16)); | ||
219 | unsafe { std::mem::transmute::<u16, SyntaxKind>(d) } | ||
220 | } | ||
221 | } | ||
222 | |||
223 | impl From<SyntaxKind> for u16 { | ||
224 | fn from(k: SyntaxKind) -> u16 { | ||
225 | k as u16 | ||
226 | } | ||
227 | } | ||
228 | |||
229 | impl SyntaxKind { | ||
230 | pub fn is_keyword(self) -> bool { | ||
231 | match self { | ||
232 | #(#keywords)|* => true, | ||
233 | _ => false, | ||
234 | } | ||
235 | } | ||
236 | |||
237 | pub fn is_punct(self) -> bool { | ||
238 | match self { | ||
239 | #(#punctuation)|* => true, | ||
240 | _ => false, | ||
241 | } | ||
242 | } | ||
243 | |||
244 | pub fn is_literal(self) -> bool { | ||
245 | match self { | ||
246 | #(#literals)|* => true, | ||
247 | _ => false, | ||
248 | } | ||
249 | } | ||
250 | |||
251 | pub(crate) fn info(self) -> &'static SyntaxInfo { | ||
252 | match self { | ||
253 | #(#punctuation => &SyntaxInfo { name: stringify!(#punctuation) },)* | ||
254 | #(#keywords => &SyntaxInfo { name: stringify!(#keywords) },)* | ||
255 | #(#literals => &SyntaxInfo { name: stringify!(#literals) },)* | ||
256 | #(#tokens => &SyntaxInfo { name: stringify!(#tokens) },)* | ||
257 | #(#nodes => &SyntaxInfo { name: stringify!(#nodes) },)* | ||
258 | TOMBSTONE => &SyntaxInfo { name: "TOMBSTONE" }, | ||
259 | EOF => &SyntaxInfo { name: "EOF" }, | ||
260 | __LAST => &SyntaxInfo { name: "__LAST" }, | ||
261 | } | ||
262 | } | ||
263 | |||
264 | pub fn from_keyword(ident: &str) -> Option<SyntaxKind> { | ||
265 | let kw = match ident { | ||
266 | #(#keywords_values => #keywords,)* | ||
267 | _ => return None, | ||
268 | }; | ||
269 | Some(kw) | ||
270 | } | ||
271 | |||
272 | pub fn from_char(c: char) -> Option<SyntaxKind> { | ||
273 | let tok = match c { | ||
274 | #(#single_byte_tokens_values => #single_byte_tokens,)* | ||
275 | _ => return None, | ||
276 | }; | ||
277 | Some(tok) | ||
278 | } | ||
279 | } | ||
280 | |||
281 | #[macro_export] | ||
282 | macro_rules! T { | ||
283 | #((#punctuation_values) => { $crate::SyntaxKind::#punctuation };)* | ||
284 | #((#keywords_idents) => { $crate::SyntaxKind::#keywords };)* | ||
285 | } | ||
286 | }; | ||
287 | |||
288 | reformat(ast) | ||
289 | } | ||
290 | |||
291 | fn reformat(text: impl std::fmt::Display) -> Result<String> { | ||
292 | let mut rustfmt = Command::new("rustfmt") | ||
293 | .arg("--config-path") | ||
294 | .arg(project_root().join("rustfmt.toml")) | ||
295 | .stdin(Stdio::piped()) | ||
296 | .stdout(Stdio::piped()) | ||
297 | .spawn()?; | ||
298 | write!(rustfmt.stdin.take().unwrap(), "{}", text)?; | ||
299 | let output = rustfmt.wait_with_output()?; | ||
300 | let stdout = String::from_utf8(output.stdout)?; | ||
301 | let preamble = "Generated file, do not edit by hand, see `crate/ra_tools/src/codegen`"; | ||
302 | Ok(format!("// {}\n\n{}", preamble, stdout)) | ||
303 | } | ||
304 | |||
305 | #[derive(Deserialize, Debug)] | ||
306 | struct Grammar { | ||
307 | single_byte_tokens: Vec<(String, String)>, | ||
308 | multi_byte_tokens: Vec<(String, String)>, | ||
309 | keywords: Vec<String>, | ||
310 | contextual_keywords: Vec<String>, | ||
311 | literals: Vec<String>, | ||
312 | tokens: Vec<String>, | ||
313 | nodes: Vec<String>, | ||
314 | ast: BTreeMap<String, AstNode>, | ||
315 | } | ||
316 | |||
317 | #[derive(Deserialize, Debug)] | ||
318 | struct AstNode { | ||
319 | #[serde(default)] | ||
320 | #[serde(rename = "enum")] | ||
321 | variants: Vec<String>, | ||
322 | |||
323 | #[serde(default)] | ||
324 | traits: Vec<String>, | ||
325 | #[serde(default)] | ||
326 | collections: Vec<(String, String)>, | ||
327 | #[serde(default)] | ||
328 | options: Vec<Attr>, | ||
329 | } | ||
330 | |||
331 | #[derive(Deserialize, Debug)] | ||
332 | #[serde(untagged)] | ||
333 | enum Attr { | ||
334 | Type(String), | ||
335 | NameType(String, String), | ||
336 | } | ||