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