Handle more depends values in vk.xml (#2511)

* add conjunctive normal form normalization

honestly, i think it might be easier to read the code if we skip all this cnf stuff and instead just keep the original boolean expression around but I don't have the context yet to comfortable making a change to both the read and write sides of the autogen

* support non-parens precedence for depends parsing

and (allof) takes precedence over or (oneof), implementing eager binding at the parser level

would be great to have tests for this to make sure its correct

* address panic formatting nit
This commit is contained in:
Martin Charles 2024-04-03 10:01:16 -05:00 committed by GitHub
parent 55556bb916
commit f84479b491
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 140 additions and 87 deletions

View File

@ -0,0 +1,71 @@
use std::ops::BitOrAssign;
pub struct ConjunctiveNormalForm<T> {
all_of: Vec<T>,
}
impl<T> ConjunctiveNormalForm<T>
where
T: for<'a> BitOrAssign<&'a T> + Clone,
{
pub fn empty() -> Self {
Self { all_of: Vec::new() }
}
pub fn take(self) -> Vec<T> {
self.all_of
}
/// Adds a conjunction (AND) to the conjunctive normal form expression.
///
/// For example, if the existing conjunctive normal form expression is represented as
/// ```
/// a and b and c
/// ```
/// the CNF of adding AND d is
/// ```
/// a and b and c and d
/// ```
pub fn add_conjunction(&mut self, item: T) {
self.all_of.push(item)
}
/// Adds a bijection (OR) to the conjunctive normal form expression.
///
/// For example, if the existing conjunctive normal form expression is represented as
/// ```
/// a and b
/// ```
///
/// the CNF of adding OR (c AND D)
/// ```
/// (a and b) or (c and d)
/// ```
/// is
/// ```
/// (a and b) or (c and d)
/// (a or (c and d)) and (b or (c and d))
/// (a or c) and (a or d) and (b or c) and (b or d)
/// ```
///
/// using the distributive law.
pub fn add_bijection(&mut self, other: Self) {
let mut result = vec![];
if self.all_of.is_empty() {
self.all_of = other.all_of;
return;
}
for outer in &self.all_of {
for inner in &other.all_of {
let mut inner = inner.clone();
inner |= outer;
result.push(inner);
}
}
self.all_of = result;
}
}

View File

@ -1,8 +1,9 @@
use super::{write_file, IndexMap, RequiresOneOf, VkRegistryData};
use crate::autogen::conjunctive_normal_form::ConjunctiveNormalForm;
use heck::ToSnakeCase;
use nom::{
branch::alt, bytes::complete::take_while1, character::complete, combinator::all_consuming,
multi::separated_list1, sequence::delimited, IResult, Parser,
multi::separated_list0, sequence::delimited, IResult, Parser,
};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{format_ident, quote};
@ -780,6 +781,44 @@ fn extensions_common_output(struct_name: Ident, members: &[ExtensionsMember]) ->
}
}
fn convert_depends_expression(
depends_expression: DependsExpression<'_>,
extensions: &IndexMap<&str, &Extension>,
) -> ConjunctiveNormalForm<RequiresOneOf> {
match depends_expression {
DependsExpression::Name(vk_name) => {
let new = dependency_chain(vk_name, extensions);
let mut result = ConjunctiveNormalForm::empty();
result.add_conjunction(new);
result
}
DependsExpression::AllOf(all_of) => {
let mut result = ConjunctiveNormalForm::empty();
for expr in all_of {
let cnf = convert_depends_expression(expr, extensions);
for it in cnf.take() {
result.add_conjunction(it);
}
}
result
}
DependsExpression::OneOf(one_of) => {
let mut result = ConjunctiveNormalForm::empty();
for expr in one_of {
let cnf = convert_depends_expression(expr, extensions);
result.add_bijection(cnf);
}
result
}
}
}
fn extensions_members(ty: &str, extensions: &IndexMap<&str, &Extension>) -> Vec<ExtensionsMember> {
extensions
.values()
@ -789,35 +828,14 @@ fn extensions_members(ty: &str, extensions: &IndexMap<&str, &Extension>) -> Vec<
let name = raw.strip_prefix("VK_").unwrap().to_snake_case();
let requires_all_of = extension.depends.as_ref().map_or_else(Vec::new, |depends| {
let depends_panic = |err| -> ! {
let depends_expression = parse_depends(depends).unwrap_or_else(|err| {
panic!(
"couldn't parse `depends=` attribute for extension `{}`: {}",
extension.name, err
"couldn't parse `depends={:?}` attribute for extension `{}`: {}",
extension.name, depends, err
)
};
});
match parse_depends(depends).unwrap_or_else(|err| depends_panic(err)) {
expr @ DependsExpression::Name(_) => {
from_one_of(vec![expr], extensions).unwrap_or_else(|err| depends_panic(err))
}
DependsExpression::OneOf(one_of) => {
from_one_of(one_of, extensions).unwrap_or_else(|err| depends_panic(err))
}
DependsExpression::AllOf(all_of) => all_of
.into_iter()
.flat_map(|expr| match expr {
expr @ DependsExpression::Name(_) => {
from_one_of(vec![expr], extensions)
.unwrap_or_else(|err| depends_panic(err))
}
DependsExpression::AllOf(_) => {
depends_panic("AllOf inside another AllOf".into())
}
DependsExpression::OneOf(one_of) => from_one_of(one_of, extensions)
.unwrap_or_else(|err| depends_panic(err)),
})
.collect(),
}
convert_depends_expression(depends_expression, extensions).take()
});
let conflicts_extensions = conflicts_extensions(&extension.name);
@ -889,49 +907,6 @@ fn extensions_members(ty: &str, extensions: &IndexMap<&str, &Extension>) -> Vec<
.collect()
}
fn from_one_of(
one_of: Vec<DependsExpression<'_>>,
extensions: &IndexMap<&str, &Extension>,
) -> Result<Vec<RequiresOneOf>, String> {
let mut requires_all_of = vec![RequiresOneOf::default()];
for expr in one_of {
match expr {
DependsExpression::Name(vk_name) => {
let new = dependency_chain(vk_name, extensions);
for requires_one_of in &mut requires_all_of {
*requires_one_of |= &new;
}
}
DependsExpression::OneOf(_) => return Err("OneOf inside another OneOf".into()),
DependsExpression::AllOf(all_of) => {
let mut new_requires_all_of = Vec::new();
for expr in all_of {
match expr {
DependsExpression::Name(vk_name) => {
let new = dependency_chain(vk_name, extensions);
let mut requires_all_of = requires_all_of.clone();
for requires_one_of in &mut requires_all_of {
*requires_one_of |= &new;
}
new_requires_all_of.extend(requires_all_of);
}
_ => return Err("More than three levels of nesting".into()),
}
}
requires_all_of = new_requires_all_of;
}
}
}
Ok(requires_all_of)
}
fn dependency_chain<'a>(
mut vk_name: &'a str,
extensions: &IndexMap<&str, &'a Extension>,
@ -982,29 +957,35 @@ fn parse_depends(depends: &str) -> Result<DependsExpression<'_>, String> {
fn term(input: &str) -> IResult<&str, DependsExpression<'_>> {
alt((
name.map(DependsExpression::Name),
delimited(complete::char('('), expression, complete::char(')')),
delimited(complete::char('('), one_of_expression, complete::char(')')),
))(input)
}
fn expression(input: &str) -> IResult<&str, DependsExpression<'_>> {
let (input, first) = term(input)?;
fn all_of_expression(input: &str) -> IResult<&str, DependsExpression<'_>> {
let (input, mut all_of) = separated_list0(complete::char('+'), term)(input)?;
if let Some(input) = input.strip_prefix('+') {
let (input, mut all_of) = separated_list1(complete::char('+'), term)(input)?;
all_of.insert(0, first);
Ok((input, DependsExpression::AllOf(all_of)))
} else if let Some(input) = input.strip_prefix(',') {
let (input, mut one_of) = separated_list1(complete::char(','), term)(input)?;
one_of.insert(0, first);
Ok((input, DependsExpression::OneOf(one_of)))
} else {
Ok((input, first))
}
Ok((input, {
if all_of.len() == 1 {
all_of.remove(0)
} else {
DependsExpression::AllOf(all_of)
}
}))
}
match all_consuming(expression)(depends) {
fn one_of_expression(input: &str) -> IResult<&str, DependsExpression<'_>> {
let (input, mut one_of) = separated_list0(complete::char(','), all_of_expression)(input)?;
Ok((input, {
if one_of.len() == 1 {
one_of.remove(0)
} else {
DependsExpression::OneOf(one_of)
}
}))
}
match all_consuming(one_of_expression)(depends) {
Ok((_, expr)) => Ok(expr),
Err(err) => Err(format!("{:?}", err)),
}

View File

@ -22,6 +22,7 @@ use vk_parse::{
Registry, RegistryChild, SpirvExtOrCap, Type, TypeSpec, TypesChild,
};
mod conjunctive_normal_form;
mod errors;
mod extensions;
mod features;