aboutsummaryrefslogtreecommitdiff
path: root/xtask/src/boilerplate_gen.rs
diff options
context:
space:
mode:
Diffstat (limited to 'xtask/src/boilerplate_gen.rs')
-rw-r--r--xtask/src/boilerplate_gen.rs348
1 files changed, 348 insertions, 0 deletions
diff --git a/xtask/src/boilerplate_gen.rs b/xtask/src/boilerplate_gen.rs
new file mode 100644
index 000000000..39f1cae66
--- /dev/null
+++ b/xtask/src/boilerplate_gen.rs
@@ -0,0 +1,348 @@
1//! FIXME: write short doc here
2
3use std::{
4 collections::BTreeMap,
5 fs,
6 io::Write,
7 process::{Command, Stdio},
8};
9
10use proc_macro2::{Punct, Spacing};
11use quote::{format_ident, quote};
12use ron;
13use serde::Deserialize;
14
15use crate::{project_root, update, Mode, Result, AST, GRAMMAR, SYNTAX_KINDS};
16
17pub 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
35fn 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
164fn 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
276fn 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)]
291struct 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)]
302struct 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)]
317enum Attr {
318 Type(String),
319 NameType(String, String),
320}
321
322fn 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
336fn 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}