From b81b19a735a902fe419dad5ccb28cab18fda8d98 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sun, 30 May 2021 13:59:18 +0200 Subject: [PATCH] generator: Parse and autogenerate version-related defines (#431) * generator: Emit deprecation warnings and documentation link for defines * generator: Parse and autogenerate version-related defines With more and more version-related defines showing up this saves us a bunch of time. --- ash/src/vk/definitions.rs | 28 +++++- ash/src/vk/macros.rs | 16 ---- generator/src/lib.rs | 189 +++++++++++++++++++++++++------------- 3 files changed, 151 insertions(+), 82 deletions(-) diff --git a/ash/src/vk/definitions.rs b/ash/src/vk/definitions.rs index 4fb1406..c0d75ac 100644 --- a/ash/src/vk/definitions.rs +++ b/ash/src/vk/definitions.rs @@ -6,11 +6,31 @@ use crate::vk::platform_types::*; use crate::vk::{ptr_chain_iter, Handle}; use std::fmt; use std::os::raw::*; -pub const API_VERSION_1_0: u32 = crate::vk::make_version(1, 0, 0); -pub const API_VERSION_1_1: u32 = crate::vk::make_version(1, 1, 0); -pub const API_VERSION_1_2: u32 = crate::vk::make_version(1, 2, 0); +#[doc = ""] +pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 { + ((major) << 22) | ((minor) << 12) | (patch) +} +#[doc = ""] +pub const fn version_major(version: u32) -> u32 { + (version) >> 22 +} +#[doc = ""] +pub const fn version_minor(version: u32) -> u32 { + ((version) >> 12) & 0x3ffu32 +} +#[doc = ""] +pub const fn version_patch(version: u32) -> u32 { + (version) & 0xfffu32 +} +#[doc = ""] +pub const API_VERSION_1_0: u32 = make_version(1, 0, 0); +#[doc = ""] +pub const API_VERSION_1_1: u32 = make_version(1, 1, 0); +#[doc = ""] +pub const API_VERSION_1_2: u32 = make_version(1, 2, 0); pub const HEADER_VERSION: u32 = 174u32; -pub const HEADER_VERSION_COMPLETE: u32 = crate::vk::make_version(1, 2, HEADER_VERSION); +#[doc = ""] +pub const HEADER_VERSION_COMPLETE: u32 = make_version(1, 2, HEADER_VERSION); #[doc = ""] pub type SampleMask = u32; #[doc = ""] diff --git a/ash/src/vk/macros.rs b/ash/src/vk/macros.rs index 0fdf7ff..cae901d 100644 --- a/ash/src/vk/macros.rs +++ b/ash/src/vk/macros.rs @@ -1,19 +1,3 @@ -#[doc = ""] -pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 { - (major << 22) | (minor << 12) | patch -} -#[doc = ""] -pub const fn version_major(version: u32) -> u32 { - version >> 22 -} -#[doc = ""] -pub const fn version_minor(version: u32) -> u32 { - (version >> 12) & 0x3ff -} -#[doc = ""] -pub const fn version_patch(version: u32) -> u32 { - version & 0xfff -} #[macro_export] macro_rules! vk_bitflags_wrapped { ($ name : ident , $ all : expr , $ flag_type : ty) => { diff --git a/generator/src/lib.rs b/generator/src/lib.rs index e25fa77..88f2673 100644 --- a/generator/src/lib.rs +++ b/generator/src/lib.rs @@ -3,8 +3,9 @@ use heck::{CamelCase, ShoutySnakeCase, SnakeCase}; use itertools::Itertools; use nom::{ - alt, character::complete::digit1, delimited, do_parse, map, named, one_of, opt, pair, preceded, - tag, terminated, + alt, + character::complete::{digit1, hex_digit1}, + complete, delimited, do_parse, map, named, one_of, opt, pair, preceded, tag, terminated, value, }; use once_cell::sync::Lazy; use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree}; @@ -26,30 +27,37 @@ pub enum CType { Bool32, } +impl CType { + fn to_string(&self) -> &'static str { + match self { + Self::USize => "usize", + Self::U32 => "u32", + Self::U64 => "u64", + Self::Float => "f32", + Self::Bool32 => "Bool32", + } + } +} + impl quote::ToTokens for CType { fn to_tokens(&self, tokens: &mut TokenStream) { - let term = match self { - CType::USize => format_ident!("usize"), - CType::U32 => format_ident!("u32"), - CType::U64 => format_ident!("u64"), - CType::Float => format_ident!("f32"), - CType::Bool32 => format_ident!("Bool32"), - }; - term.to_tokens(tokens); + format_ident!("{}", self.to_string()).to_tokens(tokens); } } named!(ctype<&str, CType>, alt!( - tag!("ULL") => { |_| CType::U64 } | - tag!("U") => { |_| CType::U32 } + value!(CType::U64, complete!(tag!("ULL"))) | + value!(CType::U32, complete!(tag!("U"))) ) ); named!(cexpr<&str, (CType, String)>, alt!( map!(cfloat, |f| (CType::Float, format!("{:.2}", f))) | - inverse_number + inverse_number | + decimal_number | + hexadecimal_number ) ); @@ -61,6 +69,17 @@ named!(decimal_number<&str, (CType, String)>, ) ); +named!(hexadecimal_number<&str, (CType, String)>, + preceded!( + alt!(tag!("0x") | tag!("0X")), + map!( + pair!(hex_digit1, ctype), + |(num, typ)| (typ, format!("0x{}{}", num.to_ascii_lowercase(), typ.to_string()) + ) + ) + ) +); + named!(inverse_number<&str, (CType, String)>, map!( delimited!( @@ -183,29 +202,6 @@ pub fn handle_nondispatchable_macro() -> TokenStream { } } } -pub fn vk_version_macros() -> TokenStream { - quote! { - #[doc = ""] - pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 { - (major << 22) | (minor << 12) | patch - } - - #[doc = ""] - pub const fn version_major(version: u32) -> u32 { - version >> 22 - } - - #[doc = ""] - pub const fn version_minor(version: u32) -> u32 { - (version >> 12) & 0x3ff - } - - #[doc = ""] - pub const fn version_patch(version: u32) -> u32 { - version & 0xfff - } - } -} pub fn vk_bitflags_wrapped_macro() -> TokenStream { quote! { #[macro_export] @@ -714,12 +710,25 @@ fn name_to_tokens(type_name: &str) -> Ident { format_ident!("{}", new_name.as_str()) } -fn map_identifier_to_rust(ident: Ident) -> TokenTree { - match ident.to_string().as_str() { - "VK_MAKE_VERSION" => { - TokenTree::Group(Group::new(Delimiter::None, quote!(crate::vk::make_version))) +/// Parses and rewrites a C literal into Rust +/// +/// If no special pattern is recognized the original literal is returned. +/// Any new conversions need to be added to the [`cexpr()`] [`nom`] parser. +/// +/// Examples: +/// - `0x3FFU` -> `0x3ffu32` +fn convert_c_literal(lit: Literal) -> Literal { + if let Ok((_, (_, rexpr))) = cexpr(&lit.to_string()) { + // lit::SynInt uses the same `.parse` method to create hexadecimal + // literals because there is no `Literal` constructor for it. + let mut stream = rexpr.parse::().unwrap().into_iter(); + // If expression rewriting succeeds this should parse into a single literal + match (stream.next(), stream.next()) { + (Some(TokenTree::Literal(l)), None) => l, + x => panic!("Stream must contain a single literal, not {:?}", x), } - s => format_ident!("{}", constant_name(s)).into(), + } else { + lit } } @@ -727,25 +736,47 @@ fn map_identifier_to_rust(ident: Ident) -> TokenTree { /// Identifiers are replaced with their Rust vk equivalent. /// /// Examples: -/// - `VK_MAKE_VERSION(1, 2, VK_HEADER_VERSION)` -> `crate::vk::make_version(1, 2, HEADER_VERSION)` +/// - `VK_MAKE_VERSION(1, 2, VK_HEADER_VERSION)` -> `make_version(1, 2, HEADER_VERSION)` /// - `2*VK_UUID_SIZE` -> `2 * UUID_SIZE` -fn convert_c_expression(c_expr: &str) -> TokenStream { - fn rewrite_token_stream(stream: TokenStream) -> TokenStream { +fn convert_c_expression(c_expr: &str, identifier_renames: &BTreeMap) -> TokenStream { + fn rewrite_token_stream( + stream: TokenStream, + identifier_renames: &BTreeMap, + ) -> TokenStream { stream .into_iter() .map(|tt| match tt { TokenTree::Group(group) => TokenTree::Group(Group::new( group.delimiter(), - rewrite_token_stream(group.stream()), + rewrite_token_stream(group.stream(), identifier_renames), )), - TokenTree::Ident(term) => map_identifier_to_rust(term), - _ => tt, + TokenTree::Ident(term) => { + let name = term.to_string(); + identifier_renames + .get(&name) + .cloned() + .unwrap_or_else(|| format_ident!("{}", constant_name(&name))) + .into() + } + TokenTree::Literal(lit) => TokenTree::Literal(convert_c_literal(lit)), + tt => tt, }) .collect::() } + let c_expr = c_expr + .parse() + .unwrap_or_else(|_| panic!("Failed to parse `{}` as Rust", c_expr)); + rewrite_token_stream(c_expr, identifier_renames) +} - let c_expr = c_expr.parse().unwrap(); - rewrite_token_stream(c_expr) +fn discard_outmost_delimiter(stream: TokenStream) -> TokenStream { + let stream = stream.into_iter().collect_vec(); + // Discard the delimiter if this stream consists of a single top-most group + if let [TokenTree::Group(group)] = stream.as_slice() { + TokenTree::Group(Group::new(Delimiter::None, group.stream())).into() + } else { + stream.into_iter().collect::() + } } impl FieldExt for vkxml::Field { @@ -1200,23 +1231,53 @@ pub fn generate_extension<'a>( }; Some(q) } -pub fn generate_define(define: &vkxml::Define) -> TokenStream { +pub fn generate_define( + define: &vkxml::Define, + identifier_renames: &mut BTreeMap, +) -> TokenStream { let name = constant_name(&define.name); let ident = format_ident!("{}", name); - let deprecated = define - .comment - .as_ref() - .map_or(false, |c| c.contains("DEPRECATED")); - if name == "NULL_HANDLE" || deprecated { + if name == "NULL_HANDLE" { quote!() } else if let Some(value) = &define.value { str::parse::(value).map_or(quote!(), |v| quote!(pub const #ident: u32 = #v;)) } else if let Some(c_expr) = &define.c_expression { - if define.defref.contains(&"VK_MAKE_VERSION".to_string()) { - let c_expr = convert_c_expression(c_expr); + if define.name.contains(&"VERSION".to_string()) { + let link = khronos_link(&define.name); + let c_expr = c_expr.trim_start_matches('\\'); + let c_expr = c_expr.replace("(uint32_t)", ""); + let c_expr = convert_c_expression(&c_expr, &identifier_renames); + let c_expr = discard_outmost_delimiter(c_expr); - quote!(pub const #ident: u32 = #c_expr;) + let deprecated = define + .comment + .as_ref() + .and_then(|c| c.strip_prefix("DEPRECATED: ")) + .map(|comment| quote!(#[deprecated = #comment])); + + let (code, ident) = if define.parameters.is_empty() { + (quote!(pub const #ident: u32 = #c_expr;), ident) + } else { + let params = define + .parameters + .iter() + .map(|param| format_ident!("{}", param)) + .map(|i| quote!(#i: u32)); + let ident = format_ident!("{}", name.to_lowercase()); + ( + quote!(pub const fn #ident(#(#params),*) -> u32 { #c_expr }), + ident, + ) + }; + + identifier_renames.insert(define.name.clone(), ident); + + quote! { + #deprecated + #[doc = #link] + #code + } } else { quote!() } @@ -1843,7 +1904,7 @@ pub fn derive_setters( let set_size_stmt = if array_size.contains("ename:") || array_size.contains('*') { // c_size should contain the same minus `ename:`-prefixed identifiers let array_size = field.c_size.as_ref().unwrap_or(array_size); - let c_size = convert_c_expression(array_size); + let c_size = convert_c_expression(array_size, &BTreeMap::new()); let inner_type = field.inner_type_tokens(); slice_param_ty_tokens = quote!([#inner_type; #c_size]); @@ -2186,9 +2247,12 @@ pub fn generate_definition( root_structs: &HashSet, bitflags_cache: &mut HashSet, const_values: &mut BTreeMap, + identifier_renames: &mut BTreeMap, ) -> Option { match *definition { - vkxml::DefinitionsElement::Define(ref define) => Some(generate_define(define)), + vkxml::DefinitionsElement::Define(ref define) => { + Some(generate_define(define, identifier_renames)) + } vkxml::DefinitionsElement::Typedef(ref typedef) => Some(generate_typedef(typedef)), vkxml::DefinitionsElement::Struct(ref _struct) => { Some(generate_struct(_struct, root_structs, union_types)) @@ -2585,6 +2649,8 @@ pub fn write_source_code>(vk_xml: &Path, src_dir: P) { }) .collect::>(); + let mut identifier_renames = BTreeMap::new(); + let root_names = root_struct_names(&definitions); let definition_code: Vec<_> = definitions .into_iter() @@ -2595,6 +2661,7 @@ pub fn write_source_code>(vk_xml: &Path, src_dir: P) { &root_names, &mut bitflags_cache, &mut const_values, + &mut identifier_renames, ) }) .collect(); @@ -2611,7 +2678,6 @@ pub fn write_source_code>(vk_xml: &Path, src_dir: P) { let bitflags_macro = vk_bitflags_wrapped_macro(); let handle_nondispatchable_macro = handle_nondispatchable_macro(); let define_handle_macro = define_handle_macro(); - let version_macros = vk_version_macros(); let ptr_chain_code = quote! { /// Iterates through the pointer chain. Includes the item that is passed into the function. @@ -2633,7 +2699,6 @@ pub fn write_source_code>(vk_xml: &Path, src_dir: P) { }; let macros_code = quote! { - #version_macros #bitflags_macro #handle_nondispatchable_macro #define_handle_macro