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.
This commit is contained in:
Marijn Suijten 2021-05-30 13:59:18 +02:00 committed by GitHub
parent 9d6bf63b4e
commit b81b19a735
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 151 additions and 82 deletions

View file

@ -6,11 +6,31 @@ use crate::vk::platform_types::*;
use crate::vk::{ptr_chain_iter, Handle}; use crate::vk::{ptr_chain_iter, Handle};
use std::fmt; use std::fmt;
use std::os::raw::*; use std::os::raw::*;
pub const API_VERSION_1_0: u32 = crate::vk::make_version(1, 0, 0); #[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_MAKE_VERSION.html>"]
pub const API_VERSION_1_1: u32 = crate::vk::make_version(1, 1, 0); pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 {
pub const API_VERSION_1_2: u32 = crate::vk::make_version(1, 2, 0); ((major) << 22) | ((minor) << 12) | (patch)
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MAJOR.html>"]
pub const fn version_major(version: u32) -> u32 {
(version) >> 22
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MINOR.html>"]
pub const fn version_minor(version: u32) -> u32 {
((version) >> 12) & 0x3ffu32
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_PATCH.html>"]
pub const fn version_patch(version: u32) -> u32 {
(version) & 0xfffu32
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_API_VERSION_1_0.html>"]
pub const API_VERSION_1_0: u32 = make_version(1, 0, 0);
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_API_VERSION_1_1.html>"]
pub const API_VERSION_1_1: u32 = make_version(1, 1, 0);
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_API_VERSION_1_2.html>"]
pub const API_VERSION_1_2: u32 = make_version(1, 2, 0);
pub const HEADER_VERSION: u32 = 174u32; pub const HEADER_VERSION: u32 = 174u32;
pub const HEADER_VERSION_COMPLETE: u32 = crate::vk::make_version(1, 2, HEADER_VERSION); #[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_HEADER_VERSION_COMPLETE.html>"]
pub const HEADER_VERSION_COMPLETE: u32 = make_version(1, 2, HEADER_VERSION);
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSampleMask.html>"] #[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSampleMask.html>"]
pub type SampleMask = u32; pub type SampleMask = u32;
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkBool32.html>"] #[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkBool32.html>"]

View file

@ -1,19 +1,3 @@
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_MAKE_VERSION.html>"]
pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 {
(major << 22) | (minor << 12) | patch
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MAJOR.html>"]
pub const fn version_major(version: u32) -> u32 {
version >> 22
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MINOR.html>"]
pub const fn version_minor(version: u32) -> u32 {
(version >> 12) & 0x3ff
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_PATCH.html>"]
pub const fn version_patch(version: u32) -> u32 {
version & 0xfff
}
#[macro_export] #[macro_export]
macro_rules! vk_bitflags_wrapped { macro_rules! vk_bitflags_wrapped {
($ name : ident , $ all : expr , $ flag_type : ty) => { ($ name : ident , $ all : expr , $ flag_type : ty) => {

View file

@ -3,8 +3,9 @@
use heck::{CamelCase, ShoutySnakeCase, SnakeCase}; use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
use itertools::Itertools; use itertools::Itertools;
use nom::{ use nom::{
alt, character::complete::digit1, delimited, do_parse, map, named, one_of, opt, pair, preceded, alt,
tag, terminated, 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 once_cell::sync::Lazy;
use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree}; use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree};
@ -26,30 +27,37 @@ pub enum CType {
Bool32, 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 { impl quote::ToTokens for CType {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
let term = match self { format_ident!("{}", self.to_string()).to_tokens(tokens);
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);
} }
} }
named!(ctype<&str, CType>, named!(ctype<&str, CType>,
alt!( alt!(
tag!("ULL") => { |_| CType::U64 } | value!(CType::U64, complete!(tag!("ULL"))) |
tag!("U") => { |_| CType::U32 } value!(CType::U32, complete!(tag!("U")))
) )
); );
named!(cexpr<&str, (CType, String)>, named!(cexpr<&str, (CType, String)>,
alt!( alt!(
map!(cfloat, |f| (CType::Float, format!("{:.2}", f))) | 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)>, named!(inverse_number<&str, (CType, String)>,
map!( map!(
delimited!( delimited!(
@ -183,29 +202,6 @@ pub fn handle_nondispatchable_macro() -> TokenStream {
} }
} }
} }
pub fn vk_version_macros() -> TokenStream {
quote! {
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_MAKE_VERSION.html>"]
pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 {
(major << 22) | (minor << 12) | patch
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MAJOR.html>"]
pub const fn version_major(version: u32) -> u32 {
version >> 22
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MINOR.html>"]
pub const fn version_minor(version: u32) -> u32 {
(version >> 12) & 0x3ff
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_PATCH.html>"]
pub const fn version_patch(version: u32) -> u32 {
version & 0xfff
}
}
}
pub fn vk_bitflags_wrapped_macro() -> TokenStream { pub fn vk_bitflags_wrapped_macro() -> TokenStream {
quote! { quote! {
#[macro_export] #[macro_export]
@ -714,12 +710,25 @@ fn name_to_tokens(type_name: &str) -> Ident {
format_ident!("{}", new_name.as_str()) format_ident!("{}", new_name.as_str())
} }
fn map_identifier_to_rust(ident: Ident) -> TokenTree { /// Parses and rewrites a C literal into Rust
match ident.to_string().as_str() { ///
"VK_MAKE_VERSION" => { /// If no special pattern is recognized the original literal is returned.
TokenTree::Group(Group::new(Delimiter::None, quote!(crate::vk::make_version))) /// 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::<TokenStream>().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. /// Identifiers are replaced with their Rust vk equivalent.
/// ///
/// Examples: /// 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` /// - `2*VK_UUID_SIZE` -> `2 * UUID_SIZE`
fn convert_c_expression(c_expr: &str) -> TokenStream { fn convert_c_expression(c_expr: &str, identifier_renames: &BTreeMap<String, Ident>) -> TokenStream {
fn rewrite_token_stream(stream: TokenStream) -> TokenStream { fn rewrite_token_stream(
stream: TokenStream,
identifier_renames: &BTreeMap<String, Ident>,
) -> TokenStream {
stream stream
.into_iter() .into_iter()
.map(|tt| match tt { .map(|tt| match tt {
TokenTree::Group(group) => TokenTree::Group(Group::new( TokenTree::Group(group) => TokenTree::Group(Group::new(
group.delimiter(), group.delimiter(),
rewrite_token_stream(group.stream()), rewrite_token_stream(group.stream(), identifier_renames),
)), )),
TokenTree::Ident(term) => map_identifier_to_rust(term), TokenTree::Ident(term) => {
_ => tt, 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::<TokenStream>() .collect::<TokenStream>()
} }
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(); fn discard_outmost_delimiter(stream: TokenStream) -> TokenStream {
rewrite_token_stream(c_expr) 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::<TokenStream>()
}
} }
impl FieldExt for vkxml::Field { impl FieldExt for vkxml::Field {
@ -1200,23 +1231,53 @@ pub fn generate_extension<'a>(
}; };
Some(q) Some(q)
} }
pub fn generate_define(define: &vkxml::Define) -> TokenStream { pub fn generate_define(
define: &vkxml::Define,
identifier_renames: &mut BTreeMap<String, Ident>,
) -> TokenStream {
let name = constant_name(&define.name); let name = constant_name(&define.name);
let ident = format_ident!("{}", 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!() quote!()
} else if let Some(value) = &define.value { } else if let Some(value) = &define.value {
str::parse::<u32>(value).map_or(quote!(), |v| quote!(pub const #ident: u32 = #v;)) str::parse::<u32>(value).map_or(quote!(), |v| quote!(pub const #ident: u32 = #v;))
} else if let Some(c_expr) = &define.c_expression { } else if let Some(c_expr) = &define.c_expression {
if define.defref.contains(&"VK_MAKE_VERSION".to_string()) { if define.name.contains(&"VERSION".to_string()) {
let c_expr = convert_c_expression(c_expr); 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 { } else {
quote!() quote!()
} }
@ -1843,7 +1904,7 @@ pub fn derive_setters(
let set_size_stmt = if array_size.contains("ename:") || array_size.contains('*') { let set_size_stmt = if array_size.contains("ename:") || array_size.contains('*') {
// c_size should contain the same minus `ename:`-prefixed identifiers // c_size should contain the same minus `ename:`-prefixed identifiers
let array_size = field.c_size.as_ref().unwrap_or(array_size); 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(); let inner_type = field.inner_type_tokens();
slice_param_ty_tokens = quote!([#inner_type; #c_size]); slice_param_ty_tokens = quote!([#inner_type; #c_size]);
@ -2186,9 +2247,12 @@ pub fn generate_definition(
root_structs: &HashSet<String, impl BuildHasher>, root_structs: &HashSet<String, impl BuildHasher>,
bitflags_cache: &mut HashSet<Ident, impl BuildHasher>, bitflags_cache: &mut HashSet<Ident, impl BuildHasher>,
const_values: &mut BTreeMap<Ident, ConstantTypeInfo>, const_values: &mut BTreeMap<Ident, ConstantTypeInfo>,
identifier_renames: &mut BTreeMap<String, Ident>,
) -> Option<TokenStream> { ) -> Option<TokenStream> {
match *definition { 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::Typedef(ref typedef) => Some(generate_typedef(typedef)),
vkxml::DefinitionsElement::Struct(ref _struct) => { vkxml::DefinitionsElement::Struct(ref _struct) => {
Some(generate_struct(_struct, root_structs, union_types)) Some(generate_struct(_struct, root_structs, union_types))
@ -2585,6 +2649,8 @@ pub fn write_source_code<P: AsRef<Path>>(vk_xml: &Path, src_dir: P) {
}) })
.collect::<HashSet<&str>>(); .collect::<HashSet<&str>>();
let mut identifier_renames = BTreeMap::new();
let root_names = root_struct_names(&definitions); let root_names = root_struct_names(&definitions);
let definition_code: Vec<_> = definitions let definition_code: Vec<_> = definitions
.into_iter() .into_iter()
@ -2595,6 +2661,7 @@ pub fn write_source_code<P: AsRef<Path>>(vk_xml: &Path, src_dir: P) {
&root_names, &root_names,
&mut bitflags_cache, &mut bitflags_cache,
&mut const_values, &mut const_values,
&mut identifier_renames,
) )
}) })
.collect(); .collect();
@ -2611,7 +2678,6 @@ pub fn write_source_code<P: AsRef<Path>>(vk_xml: &Path, src_dir: P) {
let bitflags_macro = vk_bitflags_wrapped_macro(); let bitflags_macro = vk_bitflags_wrapped_macro();
let handle_nondispatchable_macro = handle_nondispatchable_macro(); let handle_nondispatchable_macro = handle_nondispatchable_macro();
let define_handle_macro = define_handle_macro(); let define_handle_macro = define_handle_macro();
let version_macros = vk_version_macros();
let ptr_chain_code = quote! { let ptr_chain_code = quote! {
/// Iterates through the pointer chain. Includes the item that is passed into the function. /// Iterates through the pointer chain. Includes the item that is passed into the function.
@ -2633,7 +2699,6 @@ pub fn write_source_code<P: AsRef<Path>>(vk_xml: &Path, src_dir: P) {
}; };
let macros_code = quote! { let macros_code = quote! {
#version_macros
#bitflags_macro #bitflags_macro
#handle_nondispatchable_macro #handle_nondispatchable_macro
#define_handle_macro #define_handle_macro