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