generator: Handle <command> via vk-parse types and nom (#719)

For the upcoming `api` attribute in `vk.xml` commands also need to be
processed through `vk-parse` which has support for all the new
attributes, while `vkxml` is deprecated and completely untouched for
years.  This conversion unfortunately requires whipping up yet another
quick-and-dirty `nom` parser of a specific subset of C used in `vk.xml`
to describe parameter signatures.  This PR shows that conversion is
complete and provides no accidental semantic differences.

Also update `vk-parse` to `0.9` which contains a new `code` field on
`CommandParam` (`<param>` element) to be able to inspect the code
signature of individual parameters rather than parsing them out of (and
matching them back to `vk-parse`'s `params` array!) the `<command>`
/ `CommandDefinition` as a whole:
https://github.com/krolli/vk-parse/issues/25#issuecomment-1246330001
615ffb69eb
This commit is contained in:
Marijn Suijten 2023-03-21 06:30:05 +01:00 committed by GitHub
parent eaf140fcb0
commit 61b7415156
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 224 additions and 114 deletions

View file

@ -12028,7 +12028,7 @@ pub type PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR = unsafe extern "system"
pub type PFN_vkCmdSetFragmentShadingRateKHR = unsafe extern "system" fn(
command_buffer: CommandBuffer,
p_fragment_size: *const Extent2D,
combiner_ops: *const [FragmentShadingRateCombinerOpKHR; 2],
combiner_ops: *const [FragmentShadingRateCombinerOpKHR; 2usize],
);
#[derive(Clone)]
pub struct KhrFragmentShadingRateFn {
@ -12069,7 +12069,7 @@ impl KhrFragmentShadingRateFn {
unsafe extern "system" fn cmd_set_fragment_shading_rate_khr(
_command_buffer: CommandBuffer,
_p_fragment_size: *const Extent2D,
_combiner_ops: *const [FragmentShadingRateCombinerOpKHR; 2],
_combiner_ops: *const [FragmentShadingRateCombinerOpKHR; 2usize],
) {
panic!(concat!(
"Unable to load ",
@ -15787,7 +15787,7 @@ impl NvFragmentShadingRateEnumsFn {
pub type PFN_vkCmdSetFragmentShadingRateEnumNV = unsafe extern "system" fn(
command_buffer: CommandBuffer,
shading_rate: FragmentShadingRateNV,
combiner_ops: *const [FragmentShadingRateCombinerOpKHR; 2],
combiner_ops: *const [FragmentShadingRateCombinerOpKHR; 2usize],
);
#[derive(Clone)]
pub struct NvFragmentShadingRateEnumsFn {
@ -15805,7 +15805,7 @@ impl NvFragmentShadingRateEnumsFn {
unsafe extern "system" fn cmd_set_fragment_shading_rate_enum_nv(
_command_buffer: CommandBuffer,
_shading_rate: FragmentShadingRateNV,
_combiner_ops: *const [FragmentShadingRateCombinerOpKHR; 2],
_combiner_ops: *const [FragmentShadingRateCombinerOpKHR; 2usize],
) {
panic!(concat!(
"Unable to load ",

View file

@ -999,7 +999,7 @@ pub type PFN_vkCmdSetDepthBias = unsafe extern "system" fn(
);
#[allow(non_camel_case_types)]
pub type PFN_vkCmdSetBlendConstants =
unsafe extern "system" fn(command_buffer: CommandBuffer, blend_constants: *const [f32; 4]);
unsafe extern "system" fn(command_buffer: CommandBuffer, blend_constants: *const [f32; 4usize]);
#[allow(non_camel_case_types)]
pub type PFN_vkCmdSetDepthBounds = unsafe extern "system" fn(
command_buffer: CommandBuffer,
@ -2876,7 +2876,7 @@ impl DeviceFnV1_0 {
cmd_set_blend_constants: unsafe {
unsafe extern "system" fn cmd_set_blend_constants(
_command_buffer: CommandBuffer,
_blend_constants: *const [f32; 4],
_blend_constants: *const [f32; 4usize],
) {
panic!(concat!(
"Unable to load ",

View file

@ -13,7 +13,7 @@ once_cell = "1.7"
proc-macro2 = "1.0"
quote = "1.0"
regex = "1.4"
vk-parse = { version = "0.7", features = ["vkxml-convert"] }
vk-parse = { version = "0.9", features = ["vkxml-convert"] }
vkxml = "0.3"
[dependencies.syn]

View file

@ -6,11 +6,12 @@ use itertools::Itertools;
use nom::{
branch::alt,
bytes::complete::{tag, take_until, take_while1},
character::complete::{char, digit1, hex_digit1, multispace1, newline, none_of, one_of},
combinator::{map, opt, value},
error::{ParseError, VerboseError},
character::complete::{
char, digit1, hex_digit1, multispace0, multispace1, newline, none_of, one_of,
},
combinator::{map, map_res, opt, value},
multi::{many1, separated_list1},
sequence::{delimited, pair, preceded, terminated},
sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
IResult, Parser,
};
use once_cell::sync::Lazy;
@ -70,11 +71,11 @@ impl quote::ToTokens for CType {
}
}
fn parse_ctype<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, CType, E> {
fn parse_ctype(i: &str) -> IResult<&str, CType> {
(alt((value(CType::U64, tag("ULL")), value(CType::U32, tag("U")))))(i)
}
fn parse_cexpr<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, (CType, String), E> {
fn parse_cexpr(i: &str) -> IResult<&str, (CType, String)> {
(alt((
map(parse_cfloat, |f| (CType::Float, format!("{f:.2}"))),
parse_inverse_number,
@ -83,13 +84,11 @@ fn parse_cexpr<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, (CTyp
)))(i)
}
fn parse_cfloat<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, f32, E> {
fn parse_cfloat(i: &str) -> IResult<&str, f32> {
(terminated(nom::number::complete::float, one_of("fF")))(i)
}
fn parse_inverse_number<'a, E: ParseError<&'a str>>(
i: &'a str,
) -> IResult<&'a str, (CType, String), E> {
fn parse_inverse_number(i: &str) -> IResult<&str, (CType, String)> {
(map(
delimited(
char('('),
@ -112,7 +111,7 @@ fn parse_inverse_number<'a, E: ParseError<&'a str>>(
// Like a C string, but does not support quote escaping and expects at least one character.
// If needed, use https://github.com/Geal/nom/blob/8e09f0c3029d32421b5b69fb798cef6855d0c8df/tests/json.rs#L61-L81
fn parse_c_include_string<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, String, E> {
fn parse_c_include_string(i: &str) -> IResult<&str, String> {
(delimited(
char('"'),
map(many1(none_of("\"")), |c| {
@ -122,25 +121,21 @@ fn parse_c_include_string<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a
))(i)
}
fn parse_c_include<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, String, E> {
fn parse_c_include(i: &str) -> IResult<&str, String> {
(preceded(
tag("#include"),
preceded(multispace1, parse_c_include_string),
))(i)
}
fn parse_decimal_number<'a, E: ParseError<&'a str>>(
i: &'a str,
) -> IResult<&'a str, (CType, String), E> {
fn parse_decimal_number(i: &str) -> IResult<&str, (CType, String)> {
(map(
pair(digit1.map(str::to_string), parse_ctype),
|(dig, ctype)| (ctype, dig),
))(i)
}
fn parse_hexadecimal_number<'a, E: ParseError<&'a str>>(
i: &'a str,
) -> IResult<&'a str, (CType, String), E> {
fn parse_hexadecimal_number(i: &str) -> IResult<&str, (CType, String)> {
(preceded(
alt((tag("0x"), tag("0X"))),
map(pair(hex_digit1, parse_ctype), |(num, typ)| {
@ -152,19 +147,15 @@ fn parse_hexadecimal_number<'a, E: ParseError<&'a str>>(
))(i)
}
fn parse_c_identifier<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
fn parse_c_identifier(i: &str) -> IResult<&str, &str> {
take_while1(|c: char| c == '_' || c.is_alphanumeric())(i)
}
fn parse_comment_suffix<'a, E: ParseError<&'a str>>(
i: &'a str,
) -> IResult<&'a str, Option<&'a str>, E> {
fn parse_comment_suffix(i: &str) -> IResult<&str, Option<&str>> {
opt(delimited(tag("//"), take_until("\n"), newline))(i)
}
fn parse_parameter_names<'a, E: ParseError<&'a str>>(
i: &'a str,
) -> IResult<&'a str, Vec<&'a str>, E> {
fn parse_parameter_names(i: &str) -> IResult<&str, Vec<&str>> {
delimited(
char('('),
separated_list1(tag(", "), parse_c_identifier),
@ -175,9 +166,7 @@ fn parse_parameter_names<'a, E: ParseError<&'a str>>(
/// Parses a C macro define optionally prefixed by a comment and optionally
/// containing parameter names. The expression is left in the remainder
#[allow(clippy::type_complexity)]
fn parse_c_define_header<'a, E: ParseError<&'a str>>(
i: &'a str,
) -> IResult<&'a str, (Option<&'a str>, (&'a str, Option<Vec<&'a str>>)), E> {
fn parse_c_define_header(i: &str) -> IResult<&str, (Option<&str>, (&str, Option<Vec<&str>>))> {
(pair(
parse_comment_suffix,
preceded(
@ -187,6 +176,89 @@ fn parse_c_define_header<'a, E: ParseError<&'a str>>(
))(i)
}
#[derive(Debug)]
enum CReferenceType {
Value,
PointerToConst,
Pointer,
PointerToPointer,
PointerToPointerToConst,
PointerToConstPointer,
PointerToConstPointerToConst,
}
#[derive(Debug)]
struct CParameterType<'a> {
name: &'a str,
reference_type: CReferenceType,
}
fn parse_c_type(i: &str) -> IResult<&str, CParameterType> {
(map(
separated_pair(
tuple((
opt(tag("const ")),
preceded(opt(tag("struct ")), parse_c_identifier),
opt(char('*')),
)),
multispace0,
opt(pair(opt(tag("const")), char('*'))),
),
|((const_, name, firstptr), secondptr)| CParameterType {
name,
reference_type: match (firstptr, secondptr) {
(None, None) => CReferenceType::Value,
(Some(_), None) if const_.is_some() => CReferenceType::PointerToConst,
(Some(_), None) => CReferenceType::Pointer,
(Some(_), Some((Some(_), _))) if const_.is_some() => {
CReferenceType::PointerToConstPointerToConst
}
(Some(_), Some((Some(_), _))) => CReferenceType::PointerToConstPointer,
(Some(_), Some((None, _))) if const_.is_some() => {
CReferenceType::PointerToPointerToConst
}
(Some(_), Some((None, _))) => CReferenceType::PointerToPointer,
(None, Some(_)) => unreachable!(),
},
},
))(i)
}
#[derive(Debug)]
struct CParameter<'a> {
type_: CParameterType<'a>,
// Code only used to dissect the type surrounding this field name,
// not interested in the name itself.
_name: &'a str,
static_array: Option<usize>,
}
/// Parses a single C parameter instance, for example:
///
/// ```c
/// VkSparseImageMemoryRequirements2* pSparseMemoryRequirements
/// ```
fn parse_c_parameter(i: &str) -> IResult<&str, CParameter> {
(map(
separated_pair(
parse_c_type,
multispace0,
pair(
parse_c_identifier,
opt(delimited(char('['), map_res(digit1, str::parse), char(']'))),
),
),
|(type_, (name, static_array))| CParameter {
type_,
_name: name,
static_array,
},
))(i)
}
fn khronos_link<S: Display + ?Sized>(name: &S) -> Literal {
Literal::string(&format!(
"<https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/{name}.html>"
@ -317,8 +389,8 @@ impl quote::ToTokens for Constant {
}
Constant::Text(ref text) => text.to_tokens(tokens),
Constant::CExpr(ref expr) => {
let (_, (_, rexpr)) =
parse_cexpr::<VerboseError<&str>>(expr).expect("Unable to parse cexpr");
let (rem, (_, rexpr)) = parse_cexpr(expr).expect("Unable to parse cexpr");
assert!(rem.is_empty());
tokens.extend(rexpr.parse::<TokenStream>());
}
Constant::BitPos(pos) => {
@ -371,8 +443,8 @@ impl Constant {
match self {
Constant::Number(_) | Constant::Hex(_) => CType::USize,
Constant::CExpr(expr) => {
let (_, (ty, _)) =
parse_cexpr::<VerboseError<&str>>(expr).expect("Unable to parse cexpr");
let (rem, (ty, _)) = parse_cexpr(expr).expect("Unable to parse cexpr");
assert!(rem.is_empty());
ty
}
_ => unimplemented!(),
@ -476,31 +548,18 @@ pub enum FunctionType {
Device,
}
pub trait CommandExt {
/// Returns the ident in snake_case and without the 'vk' prefix.
fn function_type(&self) -> FunctionType;
///
/// Returns true if the command is a device level command. This is indicated by
/// the type of the first parameter.
fn command_ident(&self) -> Ident;
}
impl CommandExt for vkxml::Command {
fn command_ident(&self) -> Ident {
format_ident!("{}", self.name.strip_prefix("vk").unwrap().to_snake_case())
}
impl CommandExt for vk_parse::CommandDefinition {
fn function_type(&self) -> FunctionType {
let is_first_param_device = self
.param
.get(0)
.map(|field| {
matches!(
field.basetype.as_str(),
"VkDevice" | "VkCommandBuffer" | "VkQueue"
)
})
.unwrap_or(false);
match self.name.as_str() {
let is_first_param_device = self.params.get(0).map_or(false, |field| {
matches!(
field.definition.type_name.as_deref(),
Some("VkDevice" | "VkCommandBuffer" | "VkQueue")
)
});
match self.proto.name.as_str() {
"vkGetInstanceProcAddr" => FunctionType::Static,
"vkCreateInstance"
| "vkEnumerateInstanceLayerProperties"
@ -508,13 +567,8 @@ impl CommandExt for vkxml::Command {
| "vkEnumerateInstanceVersion" => FunctionType::Entry,
// This is actually not a device level function
"vkGetDeviceProcAddr" => FunctionType::Instance,
_ => {
if is_first_param_device {
FunctionType::Device
} else {
FunctionType::Instance
}
}
_ if is_first_param_device => FunctionType::Device,
_ => FunctionType::Instance,
}
}
}
@ -600,7 +654,7 @@ fn name_to_tokens(type_name: &str) -> Ident {
_ => type_name.strip_prefix("Vk").unwrap_or(type_name),
};
let new_name = new_name.replace("FlagBits", "Flags");
format_ident!("{}", new_name.as_str())
format_ident!("{}", new_name)
}
/// Parses and rewrites a C literal into Rust
@ -611,7 +665,7 @@ fn name_to_tokens(type_name: &str) -> Ident {
/// Examples:
/// - `0x3FFU` -> `0x3ffu32`
fn convert_c_literal(lit: Literal) -> Literal {
if let Ok((_, (_, rexpr))) = parse_cexpr::<VerboseError<&str>>(&lit.to_string()) {
if let Ok(("", (_, rexpr))) = parse_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();
@ -679,7 +733,7 @@ impl FieldExt for vkxml::Field {
"type" => "ty",
_ => name,
};
format_ident!("{}", name_corrected.to_snake_case().as_str())
format_ident!("{}", name_corrected.to_snake_case())
}
fn inner_type_tokens(
@ -768,11 +822,72 @@ impl FieldExt for vkxml::Field {
}
}
pub type CommandMap<'a> = HashMap<vkxml::Identifier, &'a vkxml::Command>;
impl FieldExt for vk_parse::CommandParam {
fn param_ident(&self) -> Ident {
let name = self.definition.name.as_str();
let name_corrected = match name {
"type" => "ty",
_ => name,
};
format_ident!("{}", name_corrected.to_snake_case())
}
fn inner_type_tokens(
&self,
_lifetime: Option<TokenStream>,
_inner_length: Option<usize>,
) -> TokenStream {
unimplemented!()
}
fn safe_type_tokens(
&self,
_lifetime: TokenStream,
_inner_length: Option<usize>,
) -> TokenStream {
unimplemented!()
}
fn type_tokens(&self, is_ffi_param: bool) -> TokenStream {
assert!(!self.is_void(), "{:?}", self);
let (rem, ty) = parse_c_parameter(&self.definition.code).unwrap();
assert!(rem.is_empty());
let type_name = name_to_tokens(ty.type_.name);
let inner_ty = match ty.type_.reference_type {
CReferenceType::Value => quote!(#type_name),
CReferenceType::Pointer => {
quote!(*mut #type_name)
}
CReferenceType::PointerToConst => quote!(*const #type_name),
CReferenceType::PointerToPointer => quote!(*mut *mut #type_name),
CReferenceType::PointerToPointerToConst => quote!(*mut *const #type_name),
CReferenceType::PointerToConstPointer => quote!(*const *mut #type_name),
CReferenceType::PointerToConstPointerToConst => quote!(*const *const #type_name),
};
match ty.static_array {
None => inner_ty,
Some(len) if is_ffi_param => quote!(*const [#inner_ty; #len]),
Some(len) => quote!([#inner_ty; #len]),
}
}
fn is_void(&self) -> bool {
self.definition.type_name.as_deref() == Some("void")
&& self.len.is_none()
&& !self.definition.name.starts_with('p')
}
fn is_pointer_to_static_sized_array(&self) -> bool {
unimplemented!()
}
}
pub type CommandMap<'a> = HashMap<vkxml::Identifier, &'a vk_parse::CommandDefinition>;
fn generate_function_pointers<'a>(
ident: Ident,
commands: &[&'a vkxml::Command],
commands: &[&'a vk_parse::CommandDefinition],
aliases: &HashMap<String, String>,
fn_cache: &mut HashSet<&'a str>,
) -> TokenStream {
@ -780,7 +895,7 @@ fn generate_function_pointers<'a>(
// really want to generate one function pointer.
let commands = commands
.iter()
.unique_by(|cmd| cmd.name.as_str())
.unique_by(|cmd| cmd.proto.name.as_str())
.collect::<Vec<_>>();
struct Command {
@ -796,28 +911,25 @@ fn generate_function_pointers<'a>(
let commands = commands
.iter()
.map(|cmd| {
let type_name = format_ident!("PFN_{}", cmd.name);
let name = &cmd.proto.name;
let type_name = format_ident!("PFN_{}", name);
let function_name_c = if let Some(alias_name) = aliases.get(&cmd.name) {
let function_name_c = if let Some(alias_name) = aliases.get(name) {
alias_name.to_string()
} else {
cmd.name.to_string()
name.to_string()
};
let function_name_rust = format_ident!(
"{}",
function_name_c
.strip_prefix("vk")
.unwrap()
.to_snake_case()
.as_str()
function_name_c.strip_prefix("vk").unwrap().to_snake_case()
);
let params: Vec<_> = cmd
.param
.params
.iter()
.map(|field| {
let name = field.param_ident();
let ty = field.type_tokens(true);
.map(|param| {
let name = param.param_ident();
let ty = param.type_tokens(true);
(name, ty)
})
.collect();
@ -833,19 +945,25 @@ fn generate_function_pointers<'a>(
});
let parameters_unused = quote!(#(#params_iter,)*);
let ret = cmd
.proto
.type_name
.as_ref()
.expect("Command must have return type");
Command {
// PFN function pointers are global and can not have duplicates.
// This can happen because there are aliases to commands
type_needs_defining: fn_cache.insert(cmd.name.as_str()),
type_needs_defining: fn_cache.insert(name),
type_name,
function_name_c,
function_name_rust,
parameters,
parameters_unused,
returns: if cmd.return_type.is_void() {
returns: if ret == "void" {
quote!()
} else {
let ret_ty_tokens = cmd.return_type.type_tokens(true);
let ret_ty_tokens = name_to_tokens(ret);
quote!(-> #ret_ty_tokens)
},
}
@ -1042,11 +1160,9 @@ pub fn generate_extension_commands<'a>(
for name in names {
if let Some(cmd) = cmd_map.get(name).copied() {
commands.push(cmd);
} else if let Some(cmd) = cmd_aliases
.get(name)
.and_then(|alias_name| cmd_map.get(alias_name).copied())
{
aliases.insert(cmd.name.clone(), name.to_string());
} else if let Some(cmd) = cmd_aliases.get(name) {
aliases.insert(cmd.clone(), name.to_string());
let cmd = cmd_map.get(cmd).copied().unwrap();
commands.push(cmd);
}
}
@ -1097,12 +1213,6 @@ pub fn generate_extension<'a>(
cmd_aliases: &HashMap<String, String>,
fn_cache: &mut HashSet<&'a str>,
) -> Option<TokenStream> {
// Okay this is a little bit odd. We need to generate all extensions, even disabled ones,
// because otherwise some StructureTypes won't get generated. But we don't generate extensions
// that are reserved
if extension.name.contains("RESERVED") {
return None;
}
let extension_tokens = generate_extension_constants(
&extension.name,
extension.number.unwrap_or(0),
@ -1138,8 +1248,7 @@ pub fn generate_define(
if define_name.contains("VERSION") && !spec.code.contains("//#define") {
let link = khronos_link(define_name);
let (c_expr, (comment, (_name, parameters))) =
parse_c_define_header::<VerboseError<&str>>(&spec.code).unwrap();
let (c_expr, (comment, (_name, parameters))) = parse_c_define_header(&spec.code).unwrap();
let c_expr = c_expr.trim().trim_start_matches('\\');
let c_expr = c_expr.replace("(uint32_t)", "");
let c_expr = convert_c_expression(&c_expr, identifier_renames);
@ -1348,7 +1457,7 @@ pub fn generate_enum<'a>(
let name = enum_.name.as_ref().unwrap();
let clean_name = name.strip_prefix("Vk").unwrap();
let clean_name = clean_name.replace("FlagBits", "Flags");
let ident = format_ident!("{}", clean_name.as_str());
let ident = format_ident!("{}", clean_name);
let constants = enum_
.children
.iter()
@ -1375,7 +1484,7 @@ pub fn generate_enum<'a>(
let khronos_link = khronos_link(name);
if name.contains("Bit") {
let ident = format_ident!("{}", clean_name.as_str());
let ident = format_ident!("{}", clean_name);
let type_ = if enum_.bitwidth == Some(64u32) {
quote!(Flags64)
@ -1430,9 +1539,9 @@ pub fn generate_enum<'a>(
pub fn generate_result(ident: Ident, enum_: &vk_parse::Enums) -> TokenStream {
let notation = enum_.children.iter().filter_map(|elem| {
let (variant_name, notation) = match *elem {
vk_parse::EnumsChild::Enum(ref constant) => (
constant.name.as_str(),
let (variant_name, notation) = match elem {
vk_parse::EnumsChild::Enum(constant) => (
&constant.name,
constant.formatted_notation().unwrap_or(Cow::Borrowed("")),
),
_ => {
@ -1776,7 +1885,7 @@ pub fn derive_setters(
array_size
};
let array_size_ident = format_ident!("{}", array_size.to_snake_case().as_str());
let array_size_ident = format_ident!("{}", array_size.to_snake_case());
let size_field = members.clone().find(|m| m.name.as_deref() == Some(array_size)).unwrap();
@ -2096,7 +2205,7 @@ pub fn generate_handle(handle: &vkxml::Handle) -> Option<TokenStream> {
Some(tokens)
}
fn generate_funcptr(fnptr: &vkxml::FunctionPointer) -> TokenStream {
let name = format_ident!("{}", fnptr.name.as_str());
let name = format_ident!("{}", fnptr.name);
let ret_ty_tokens = if fnptr.return_type.is_void() {
quote!()
} else {
@ -2420,7 +2529,7 @@ pub fn extract_native_types(registry: &vk_parse::Registry) -> (Vec<(String, Stri
"Header `{name}` being redefined",
);
let (rem, path) = parse_c_include::<VerboseError<&str>>(&code.code)
let (rem, path) = parse_c_include(&code.code)
.expect("Failed to parse `#include` from `category=\"include\"` directive");
assert!(rem.is_empty());
header_includes.push((name, path));
@ -2490,12 +2599,13 @@ pub fn write_source_code<P: AsRef<Path>>(vk_headers_dir: &Path, src_dir: P) {
.map(|(name, alias)| (name.to_string(), alias.to_string()))
.collect();
let commands: HashMap<vkxml::Identifier, &vkxml::Command> = spec
.elements
let commands: CommandMap<'_> = spec2
.0
.iter()
.filter_map(get_variant!(vkxml::RegistryElement::Commands))
.flat_map(|cmds| &cmds.elements)
.map(|cmd| (cmd.name.clone(), cmd))
.filter_map(get_variant!(vk_parse::RegistryChild::Commands))
.flat_map(|cmds| &cmds.children)
.filter_map(get_variant!(vk_parse::Command::Definition))
.map(|cmd| (cmd.proto.name.clone(), cmd))
.collect();
let features: Vec<&vkxml::Feature> = spec