diff options
author | Aleksey Kladov <[email protected]> | 2019-08-18 21:11:08 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-08-18 21:11:08 +0100 |
commit | 8cefdb5527d011d7d5ca2902791b7c3da0276fec (patch) | |
tree | b40d906a23b8ffa37b40edd4916a13fd9e6f4ab0 /crates/ra_tools | |
parent | d545a5c75cb181758dd745b031eacfd7fc8a6929 (diff) |
use quote! macro to generate grammar
We already use syn"e elsewhere (transitively), so it make sense to
cut down on the number of technologies and get rid of tera
Diffstat (limited to 'crates/ra_tools')
-rw-r--r-- | crates/ra_tools/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_tools/src/codegen.rs | 165 |
2 files changed, 153 insertions, 13 deletions
diff --git a/crates/ra_tools/Cargo.toml b/crates/ra_tools/Cargo.toml index ab9fa5d86..1bb6fb71c 100644 --- a/crates/ra_tools/Cargo.toml +++ b/crates/ra_tools/Cargo.toml | |||
@@ -12,4 +12,5 @@ itertools = "0.8.0" | |||
12 | clap = "2.32.0" | 12 | clap = "2.32.0" |
13 | quote = "1.0.2" | 13 | quote = "1.0.2" |
14 | ron = "0.5.1" | 14 | ron = "0.5.1" |
15 | heck = "0.3.0" | ||
15 | serde = { version = "1.0.0", features = ["derive"] } | 16 | serde = { version = "1.0.0", features = ["derive"] } |
diff --git a/crates/ra_tools/src/codegen.rs b/crates/ra_tools/src/codegen.rs index f0a54808a..e14092704 100644 --- a/crates/ra_tools/src/codegen.rs +++ b/crates/ra_tools/src/codegen.rs | |||
@@ -1,27 +1,162 @@ | |||
1 | use std::{collections::BTreeMap, fs, path::Path}; | 1 | use std::{ |
2 | collections::BTreeMap, | ||
3 | fs, | ||
4 | io::Write, | ||
5 | path::Path, | ||
6 | process::{Command, Stdio}, | ||
7 | }; | ||
2 | 8 | ||
3 | use quote::quote; | 9 | use heck::{ShoutySnakeCase, SnakeCase}; |
10 | use quote::{format_ident, quote}; | ||
4 | use ron; | 11 | use ron; |
5 | use serde::Deserialize; | 12 | use serde::Deserialize; |
6 | 13 | ||
7 | use crate::{project_root, Mode, Result, AST, GRAMMAR}; | 14 | use crate::{project_root, Mode, Result, AST, GRAMMAR, SYNTAX_KINDS}; |
8 | 15 | ||
9 | pub fn generate(mode: Mode) -> Result<()> { | 16 | pub fn generate(mode: Mode) -> Result<()> { |
10 | let grammar = project_root().join(GRAMMAR); | 17 | let grammar = project_root().join(GRAMMAR); |
11 | // let syntax_kinds = project_root().join(SYNTAX_KINDS); | 18 | let grammar: Grammar = { |
12 | let ast = project_root().join(AST); | 19 | let text = fs::read_to_string(grammar)?; |
13 | generate_ast(&grammar, &ast, mode) | ||
14 | } | ||
15 | |||
16 | fn generate_ast(grammar_src: &Path, dst: &Path, mode: Mode) -> Result<()> { | ||
17 | let src: Grammar = { | ||
18 | let text = fs::read_to_string(grammar_src)?; | ||
19 | ron::de::from_str(&text)? | 20 | ron::de::from_str(&text)? |
20 | }; | 21 | }; |
21 | eprintln!("{:#?}", src); | 22 | |
23 | let _syntax_kinds = project_root().join(SYNTAX_KINDS); | ||
24 | let _ast = project_root().join(AST); | ||
25 | |||
26 | let ast = generate_ast(&grammar)?; | ||
27 | println!("{}", ast); | ||
22 | Ok(()) | 28 | Ok(()) |
23 | } | 29 | } |
24 | 30 | ||
31 | fn generate_ast(grammar: &Grammar) -> Result<String> { | ||
32 | let nodes = grammar.ast.iter().map(|(name, ast_node)| { | ||
33 | let variants = | ||
34 | ast_node.variants.iter().map(|var| format_ident!("{}", var)).collect::<Vec<_>>(); | ||
35 | let name = format_ident!("{}", name); | ||
36 | |||
37 | let kinds = if variants.is_empty() { vec![name.clone()] } else { variants.clone() } | ||
38 | .into_iter() | ||
39 | .map(|name| format_ident!("{}", name.to_string().to_shouty_snake_case())) | ||
40 | .collect::<Vec<_>>(); | ||
41 | |||
42 | let variants = if variants.is_empty() { | ||
43 | None | ||
44 | } else { | ||
45 | let kind_enum = format_ident!("{}Kind", name); | ||
46 | Some(quote!( | ||
47 | pub enum #kind_enum { | ||
48 | #(#variants(#variants),)* | ||
49 | } | ||
50 | |||
51 | #( | ||
52 | impl From<#variants> for #name { | ||
53 | fn from(node: #variants) -> #name { | ||
54 | #name { syntax: node.syntax } | ||
55 | } | ||
56 | } | ||
57 | )* | ||
58 | |||
59 | impl #name { | ||
60 | pub fn kind(&self) -> #kind_enum { | ||
61 | let syntax = self.syntax.clone(); | ||
62 | match syntax.kind() { | ||
63 | #( | ||
64 | #kinds => | ||
65 | #kind_enum::#variants(#variants { syntax }), | ||
66 | )* | ||
67 | _ => unreachable!(), | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | )) | ||
72 | }; | ||
73 | |||
74 | let traits = ast_node.traits.iter().map(|trait_name| { | ||
75 | let trait_name = format_ident!("{}", trait_name); | ||
76 | quote!(impl ast::#trait_name for #name {}) | ||
77 | }); | ||
78 | |||
79 | let collections = ast_node.collections.iter().map(|(name, kind)| { | ||
80 | let method_name = format_ident!("{}", name); | ||
81 | let kind = format_ident!("{}", kind); | ||
82 | quote! { | ||
83 | pub fn #method_name(&self) -> AstChildren<#kind> { | ||
84 | AstChildren::new(&self.syntax) | ||
85 | } | ||
86 | } | ||
87 | }); | ||
88 | |||
89 | let options = ast_node.options.iter().map(|attr| { | ||
90 | let method_name = match attr { | ||
91 | Attr::Type(t) => format_ident!("{}", t.to_snake_case()), | ||
92 | Attr::NameType(n, _) => format_ident!("{}", n), | ||
93 | }; | ||
94 | let ty = match attr { | ||
95 | Attr::Type(t) | Attr::NameType(_, t) => format_ident!("{}", t), | ||
96 | }; | ||
97 | quote! { | ||
98 | pub fn #method_name(&self) -> Option<#ty> { | ||
99 | AstChildren::new(&self.syntax).next() | ||
100 | } | ||
101 | } | ||
102 | }); | ||
103 | |||
104 | quote! { | ||
105 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
106 | pub struct #name { | ||
107 | pub(crate) syntax: SyntaxNode, | ||
108 | } | ||
109 | |||
110 | impl AstNode for #name { | ||
111 | fn can_cast(kind: SyntaxKind) -> bool { | ||
112 | match kind { | ||
113 | #(#kinds)|* => true, | ||
114 | _ => false, | ||
115 | } | ||
116 | } | ||
117 | fn cast(syntax: SyntaxNode) -> Option<Self> { | ||
118 | if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } | ||
119 | } | ||
120 | fn syntax(&self) -> &SyntaxNode { &self.syntax } | ||
121 | } | ||
122 | |||
123 | #variants | ||
124 | |||
125 | #(#traits)* | ||
126 | |||
127 | impl #name { | ||
128 | #(#collections)* | ||
129 | #(#options)* | ||
130 | } | ||
131 | } | ||
132 | }); | ||
133 | |||
134 | let ast = quote! { | ||
135 | use crate::{ | ||
136 | SyntaxNode, SyntaxKind::{self, *}, | ||
137 | ast::{self, AstNode, AstChildren}, | ||
138 | }; | ||
139 | |||
140 | #(#nodes)* | ||
141 | }; | ||
142 | |||
143 | let pretty = reformat(ast)?; | ||
144 | Ok(pretty) | ||
145 | } | ||
146 | |||
147 | fn reformat(text: impl std::fmt::Display) -> Result<String> { | ||
148 | let mut rustfmt = Command::new("rustfmt") | ||
149 | .arg("--config-path") | ||
150 | .arg(project_root().join("rustfmt.toml")) | ||
151 | .stdin(Stdio::piped()) | ||
152 | .stdout(Stdio::piped()) | ||
153 | .spawn()?; | ||
154 | write!(rustfmt.stdin.take().unwrap(), "{}", text)?; | ||
155 | let output = rustfmt.wait_with_output()?; | ||
156 | let stdout = String::from_utf8(output.stdout)?; | ||
157 | Ok(stdout) | ||
158 | } | ||
159 | |||
25 | #[derive(Deserialize, Debug)] | 160 | #[derive(Deserialize, Debug)] |
26 | struct Grammar { | 161 | struct Grammar { |
27 | single_byte_tokens: Vec<(String, String)>, | 162 | single_byte_tokens: Vec<(String, String)>, |
@@ -36,9 +171,13 @@ struct Grammar { | |||
36 | #[derive(Deserialize, Debug)] | 171 | #[derive(Deserialize, Debug)] |
37 | struct AstNode { | 172 | struct AstNode { |
38 | #[serde(default)] | 173 | #[serde(default)] |
174 | #[serde(rename = "enum")] | ||
175 | variants: Vec<String>, | ||
176 | |||
177 | #[serde(default)] | ||
39 | traits: Vec<String>, | 178 | traits: Vec<String>, |
40 | #[serde(default)] | 179 | #[serde(default)] |
41 | collections: Vec<Attr>, | 180 | collections: Vec<(String, String)>, |
42 | #[serde(default)] | 181 | #[serde(default)] |
43 | options: Vec<Attr>, | 182 | options: Vec<Attr>, |
44 | } | 183 | } |