b3d88846866f41b0df9a20e3fd60faad8488ff3c
[kazan.git] / spirv-parser-generator / src / generate.rs
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 // Copyright 2018 Jacob Lifshay
3
4 use ast;
5 use proc_macro2;
6 use std::borrow::Cow;
7 use std::collections::HashMap;
8 use std::fmt;
9 use std::io::{self, Read, Write};
10 use std::process::{Child, Command, ExitStatus, Stdio};
11 use std::thread;
12 use util::{self, NameFormat::*};
13 use which;
14 use Error;
15 use Options;
16
17 #[derive(Debug)]
18 enum FormatError {
19 IOError(io::Error),
20 WhichError(which::Error),
21 RustFmtFailed(ExitStatus),
22 }
23
24 impl fmt::Display for FormatError {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26 match self {
27 FormatError::IOError(v) => fmt::Display::fmt(v, f),
28 FormatError::WhichError(v) => fmt::Display::fmt(v, f),
29 FormatError::RustFmtFailed(v) => write!(f, "rustfmt failed: {:?}", v),
30 }
31 }
32 }
33
34 impl From<which::Error> for FormatError {
35 fn from(v: which::Error) -> Self {
36 FormatError::WhichError(v)
37 }
38 }
39
40 impl From<io::Error> for FormatError {
41 fn from(v: io::Error) -> Self {
42 FormatError::IOError(v)
43 }
44 }
45
46 fn format_source<'a>(options: &Options, source: &'a str) -> Result<Cow<'a, str>, FormatError> {
47 if !options.run_rustfmt {
48 return Ok(Cow::Borrowed(source));
49 }
50 let rustfmt_path = match options.rustfmt_path.clone() {
51 Some(v) => v,
52 None => which::which("rustfmt")?,
53 };
54 let mut command = Command::new(rustfmt_path)
55 .stdin(Stdio::piped())
56 .stdout(Stdio::piped())
57 .spawn()?;
58 let stdin = command.stdin.take().unwrap();
59 let reader_thread = thread::spawn(move || -> io::Result<(String, Child)> {
60 let mut output = String::new();
61 command.stdout.take().unwrap().read_to_string(&mut output)?;
62 Ok((output, command))
63 });
64 { stdin }.write_all(source.as_bytes())?;
65 let (output, mut command) = reader_thread.join().unwrap()?;
66 let exit_status = command.wait()?;
67 if exit_status.success() {
68 Ok(Cow::Owned(output))
69 } else {
70 Err(FormatError::RustFmtFailed(exit_status))
71 }
72 }
73
74 fn remove_initial_op(name: &str) -> &str {
75 const INITIAL_OP: &str = "Op";
76 assert!(name.starts_with(INITIAL_OP));
77 &name[INITIAL_OP.len()..]
78 }
79
80 fn new_id<T: AsRef<str>>(name: T, name_format: util::NameFormat) -> proc_macro2::Ident {
81 proc_macro2::Ident::new(
82 &name_format
83 .name_from_words(util::WordIterator::new(name.as_ref()))
84 .unwrap(),
85 proc_macro2::Span::call_site(),
86 )
87 }
88
89 fn new_enumerant_id<T1: AsRef<str>, T2: AsRef<str>>(
90 enum_name: T1,
91 enumerant_name: T2,
92 ) -> proc_macro2::Ident {
93 let enumerant_name_words = util::WordIterator::new(enumerant_name.as_ref());
94 let enumerant_name_first_word = enumerant_name_words.clone().next();
95 let name = if enumerant_name_first_word
96 .map(str::chars)
97 .as_mut()
98 .and_then(Iterator::next)
99 .filter(char::is_ascii_digit)
100 .is_some()
101 {
102 CamelCase
103 .name_from_words(
104 util::WordIterator::new(enum_name.as_ref()).chain(enumerant_name_words),
105 )
106 .unwrap()
107 } else {
108 CamelCase.name_from_words(enumerant_name_words).unwrap()
109 };
110 proc_macro2::Ident::new(&name, proc_macro2::Span::call_site())
111 }
112
113 fn new_combined_id<I: IntoIterator>(names: I, name_format: util::NameFormat) -> proc_macro2::Ident
114 where
115 I::Item: AsRef<str>,
116 {
117 let names: Vec<I::Item> = names.into_iter().collect();
118 proc_macro2::Ident::new(
119 &name_format
120 .name_from_words(
121 names
122 .iter()
123 .map(AsRef::as_ref)
124 .flat_map(util::WordIterator::new),
125 )
126 .unwrap(),
127 proc_macro2::Span::call_site(),
128 )
129 }
130
131 #[cfg_attr(feature = "cargo-clippy", allow(clippy::cyclomatic_complexity))]
132 pub(crate) fn generate(
133 core_grammar: ast::CoreGrammar,
134 parsed_extension_instruction_sets: HashMap<
135 super::ExtensionInstructionSet,
136 ast::ExtensionInstructionSet,
137 >,
138 options: &Options,
139 ) -> Result<String, Error> {
140 let mut out = Vec::new();
141 let ast::CoreGrammar {
142 copyright: core_grammar_copyright,
143 magic_number,
144 major_version,
145 minor_version,
146 revision: core_revision,
147 instructions: core_instructions,
148 operand_kinds,
149 } = core_grammar;
150 writeln!(&mut out, "// automatically generated file")?;
151 writeln!(&mut out, "//")?;
152 for i in &core_grammar_copyright {
153 assert_eq!(i.find('\r'), None);
154 assert_eq!(i.find('\n'), None);
155 if i == "" {
156 writeln!(&mut out, "//");
157 } else {
158 writeln!(&mut out, "// {}", i);
159 }
160 }
161 writeln!(
162 &mut out,
163 "{}",
164 quote!{
165 pub const MAGIC_NUMBER: u32 = #magic_number;
166 pub const MAJOR_VERSION: u32 = #major_version;
167 pub const MINOR_VERSION: u32 = #minor_version;
168 pub const REVISION: u32 = #core_revision;
169 }
170 )?;
171 for operand_kind in &operand_kinds {
172 match operand_kind {
173 ast::OperandKind::BitEnum { kind, enumerants } => {
174 let mut enumerant_members = Vec::new();
175 let mut enumerant_member_names = Vec::new();
176 let mut enumerant_items = Vec::new();
177 for enumerant in enumerants {
178 if enumerant.value.0 == 0 {
179 continue;
180 }
181 let member_name = new_id(&enumerant.enumerant, SnakeCase);
182 enumerant_member_names.push(member_name.clone());
183 let type_name =
184 new_combined_id(&[kind.as_ref(), &enumerant.enumerant], CamelCase);
185 if enumerant.parameters.is_empty() {
186 enumerant_items.push(quote!{
187 #[derive(Clone, Debug, Default)]
188 pub struct #type_name;
189 });
190 } else {
191 let parameters = enumerant.parameters.iter().map(|parameter| {
192 let kind = new_id(&parameter.kind, CamelCase);
193 quote!{
194 pub #kind,
195 }
196 });
197 enumerant_items.push(quote!{
198 #[derive(Clone, Debug, Default)]
199 pub struct #type_name(#(#parameters)*);
200 });
201 }
202 enumerant_members.push(quote!{
203 pub #member_name: Option<#type_name>
204 });
205 }
206 let kind_id = new_id(kind, CamelCase);
207 writeln!(
208 &mut out,
209 "{}",
210 quote!{
211 #[derive(Clone, Debug, Default)]
212 pub struct #kind_id {
213 #(#enumerant_members),*
214 }
215 impl #kind_id {
216 pub fn new() -> Self {
217 Self {
218 #(#enumerant_member_names: None,)*
219 }
220 }
221 }
222 #(#enumerant_items)*
223 }
224 )?;
225 }
226 ast::OperandKind::ValueEnum { kind, enumerants } => {
227 let kind_id = new_id(&kind, CamelCase);
228 let mut generated_enumerants = Vec::new();
229 for enumerant in enumerants {
230 let name = new_enumerant_id(&kind, &enumerant.enumerant);
231 if enumerant.parameters.is_empty() {
232 generated_enumerants.push(quote!{#name});
233 continue;
234 }
235 }
236 writeln!(
237 &mut out,
238 "{}",
239 quote!{
240 #[derive(Clone, Debug)]
241 pub enum #kind_id {
242 #(#generated_enumerants,)*
243 }
244 }
245 )?;
246 }
247 ast::OperandKind::Id { kind, doc: _ } => {
248 let base = if *kind == ast::Kind::IdRef {
249 quote!{u32}
250 } else {
251 quote!{IdRef}
252 };
253 let kind_id = new_id(kind, CamelCase);
254 writeln!(
255 &mut out,
256 "{}",
257 quote!{
258 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
259 #[repr(transparent)]
260 pub struct #kind_id(pub #base);
261 }
262 )?;
263 }
264 ast::OperandKind::Literal { kind, doc: _ } => {
265 let kind_id = new_id(kind, CamelCase);
266 writeln!(
267 &mut out,
268 "{}",
269 match kind {
270 ast::LiteralKind::LiteralInteger
271 | ast::LiteralKind::LiteralContextDependentNumber => unreachable!(),
272 ast::LiteralKind::LiteralInteger32
273 | ast::LiteralKind::LiteralContextDependentNumber32 => {
274 quote!{pub type #kind_id = u32;}
275 }
276 ast::LiteralKind::LiteralInteger64
277 | ast::LiteralKind::LiteralContextDependentNumber64 => {
278 quote!{pub type #kind_id = u64;}
279 }
280 ast::LiteralKind::LiteralString => quote!{pub type #kind_id = String;},
281 ast::LiteralKind::LiteralExtInstInteger => {
282 quote!{pub type #kind_id = u32;}
283 }
284 ast::LiteralKind::LiteralSpecConstantOpInteger => continue,
285 }
286 )?;
287 }
288 ast::OperandKind::Composite { kind, bases } => {
289 let kind = new_id(kind, CamelCase);
290 let bases = bases.into_iter().map(|base| new_id(base, CamelCase));
291 writeln!(&mut out, "{}", quote!{pub type #kind = (#(#bases),*);})?;
292 }
293 }
294 }
295 {
296 let mut instruction_enumerants = Vec::new();
297 let mut spec_constant_op_instruction_enumerants = Vec::new();
298 for instruction in core_instructions.iter() {
299 let opname = new_id(remove_initial_op(instruction.opname.as_ref()), CamelCase);
300 let instruction_enumerant =
301 if instruction.opname == ast::InstructionName::OpSpecConstantOp {
302 quote!{
303 #opname {
304 operation: OpSpecConstantOp,
305 }
306 }
307 } else if instruction.operands.is_empty() {
308 quote!{#opname}
309 } else {
310 let mut fields = Vec::new();
311 for operand in instruction.operands.iter() {
312 let kind = new_id(&operand.kind, CamelCase);
313 let name = new_id(operand.name.as_ref().unwrap(), SnakeCase);
314 let kind = match &operand.quantifier {
315 None => quote!{#kind},
316 Some(ast::Quantifier::Optional) => quote!{Option<#kind>},
317 Some(ast::Quantifier::Variadic) => quote!{Vec<#kind>},
318 };
319 fields.push(quote!{#name: #kind});
320 }
321 quote!{
322 #opname {
323 #(#fields,)*
324 }
325 }
326 };
327 if ast::OP_SPEC_CONSTANT_OP_SUPPORTED_INSTRUCTIONS.contains(&instruction.opname) {
328 spec_constant_op_instruction_enumerants.push(instruction_enumerant.clone());
329 }
330 instruction_enumerants.push(instruction_enumerant);
331 }
332 writeln!(
333 &mut out,
334 "{}",
335 quote!{
336 #[derive(Clone, Debug)]
337 pub enum OpSpecConstantOp {
338 #(#spec_constant_op_instruction_enumerants,)*
339 }
340 #[derive(Clone, Debug)]
341 pub enum Instruction {
342 #(#instruction_enumerants,)*
343 }
344 }
345 )?;
346 }
347 let source = String::from_utf8(out).unwrap();
348 let source = match format_source(&options, &source) {
349 Ok(source) => source.into_owned(),
350 Err(error) => {
351 eprintln!("formatting source failed: {}", error);
352 source.clone()
353 }
354 };
355 Ok(source)
356 }