alex/volcanic
Automated vulkan bindings
BRANCH main
SNAPSHOT Improved loading api and made ready for publish
DOWNLOAD
root codegen src registry.rs
use crate::ctype;
#[derive(Debug)]
pub struct Registry {
pub types: Vec<TypeDef>,
pub enum_groups: Vec<EnumGroup>,
pub commands: Vec<Command>,
pub features: Vec<Feature>,
pub extensions: Vec<Extension>,
}
#[derive(Debug, Clone)]
pub struct TypeDef {
pub name: String,
pub category: String,
pub api: Option<String>,
pub alias: Option<String>,
pub parent: Option<String>,
pub requires: Option<String>,
pub bitvalues: Option<String>,
pub members: Vec<ctype::Decl>,
pub func: Option<FuncPointer>,
pub handle_macro: Option<String>,
pub underlying_type: Option<String>,
pub underlying_decl: Option<ctype::Decl>,
pub define_text: Option<String>,
}
#[derive(Debug, Clone)]
pub struct FuncPointer {
pub proto: ctype::Decl,
pub params: Vec<ctype::Decl>,
}
#[derive(Debug, Clone)]
pub struct EnumGroup {
pub name: Option<String>,
pub kind: String,
pub bitwidth: Option<u32>,
pub entries: Vec<EnumEntry>,
}
#[derive(Debug, Clone)]
pub struct EnumEntry {
pub name: String,
pub value: Option<String>,
pub bitpos: Option<u32>,
pub offset: Option<u32>,
pub alias: Option<String>,
pub extends: Option<String>,
pub extnumber: Option<u32>,
pub dir: Option<String>,
pub ty: Option<String>,
pub api: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Command {
pub name: String,
pub api: Option<String>,
pub alias: Option<String>,
pub proto: Option<ctype::Decl>,
pub params: Vec<ctype::Decl>,
}
#[derive(Debug, Clone)]
pub struct Feature {
pub name: String,
pub api: String,
pub apitype: Option<String>,
pub depends: Option<String>,
pub requires: Vec<Require>,
}
#[derive(Debug, Clone)]
pub struct Extension {
pub name: String,
pub number: u32,
pub supported: String,
pub depends: Option<String>,
pub requires: Vec<Require>,
}
#[derive(Debug, Clone)]
pub struct Require {
pub api: Option<String>,
pub depends: Option<String>,
pub types: Vec<String>,
pub commands: Vec<String>,
pub enums: Vec<EnumEntry>,
}
impl Registry {
pub fn load(path: impl AsRef<std::path::Path>) -> crate::Result<Self> {
let path = path.as_ref();
let xml = std::fs::read_to_string(path)
.map_err(|error| format!("{}: {error}", path.display()))?;
Self::parse(&xml).map_err(|error| format!("{}: {error}", path.display()).into())
}
fn parse(xml: &str) -> crate::Result<Self> {
let doc = roxmltree::Document::parse(xml)?;
let root = doc.root_element();
if !root.has_tag_name("registry") {
return Err("expected a <registry> root element".into());
}
let mut types = Vec::new();
for node in root
.children()
.filter(|n| n.has_tag_name("types"))
.flat_map(|n| n.children().filter(|c| c.has_tag_name("type")))
{
types.push(parse_type(node));
}
let mut enum_groups = Vec::new();
for node in root.children().filter(|n| n.has_tag_name("enums")) {
enum_groups.push(parse_enum_group(node));
}
let mut commands = Vec::new();
for node in root
.children()
.filter(|n| n.has_tag_name("commands"))
.flat_map(|n| n.children().filter(|c| c.has_tag_name("command")))
{
commands.push(parse_command(node));
}
let mut features = Vec::new();
for node in root.children().filter(|n| n.has_tag_name("feature")) {
features.push(parse_feature(node));
}
let mut extensions = Vec::new();
for node in root
.children()
.filter(|n| n.has_tag_name("extensions"))
.flat_map(|n| n.children().filter(|c| c.has_tag_name("extension")))
{
extensions.push(parse_extension(node));
}
Ok(Self {
types,
enum_groups,
commands,
features,
extensions,
})
}
}
fn parse_type(node: roxmltree::Node<'_, '_>) -> TypeDef {
let category = node.attribute("category").unwrap_or("none").to_string();
let name = node
.attribute("name")
.map(ToOwned::to_owned)
.or_else(|| child_text(node, "name"))
.or_else(|| {
(category == "funcpointer").then(|| {
let proto = node
.children()
.find(|n| n.has_tag_name("proto"))
.expect("funcpointer without proto");
child_text(proto, "name").expect("funcpointer proto without name")
})
})
.unwrap_or_else(|| panic!("type without name/category body: {:?}", c_text(node).trim()));
let members = if category == "struct" || category == "union" {
node.children()
.filter(|n| n.has_tag_name("member"))
.map(ctype::parse_decl)
.collect()
} else {
Vec::new()
};
let func = if category == "funcpointer" {
let proto = node
.children()
.find(|n| n.has_tag_name("proto"))
.map(ctype::parse_decl)
.expect("funcpointer without proto");
let params = node
.children()
.filter(|n| n.has_tag_name("param"))
.map(ctype::parse_decl)
.collect();
Some(FuncPointer { proto, params })
} else {
None
};
let handle_macro = if category == "handle" {
child_text(node, "type")
} else {
None
};
let underlying_type = if category == "basetype" || category == "bitmask" {
child_text(node, "type")
} else {
None
};
let underlying_decl = if category == "basetype"
&& node.children().any(|n| n.has_tag_name("type"))
&& node.children().any(|n| n.has_tag_name("name"))
{
Some(ctype::parse_decl(node))
} else {
None
};
let define_text = if category == "define" {
Some(c_text(node))
} else {
None
};
TypeDef {
name,
category,
api: node.attribute("api").map(ToOwned::to_owned),
alias: node.attribute("alias").map(ToOwned::to_owned),
parent: node.attribute("parent").map(ToOwned::to_owned),
requires: node.attribute("requires").map(ToOwned::to_owned),
bitvalues: node.attribute("bitvalues").map(ToOwned::to_owned),
members,
func,
handle_macro,
underlying_type,
underlying_decl,
define_text,
}
}
fn parse_enum_group(node: roxmltree::Node<'_, '_>) -> EnumGroup {
let entries = node
.children()
.filter(|n| n.has_tag_name("enum"))
.filter_map(parse_enum_entry)
.collect();
EnumGroup {
name: node.attribute("name").map(ToOwned::to_owned),
kind: node.attribute("type").unwrap_or("constants").to_string(),
bitwidth: node.attribute("bitwidth").map(|v| v.parse().unwrap()),
entries,
}
}
fn parse_command(node: roxmltree::Node<'_, '_>) -> Command {
if let Some(alias) = node.attribute("alias") {
return Command {
name: node.attribute("name").unwrap().to_string(),
api: node.attribute("api").map(ToOwned::to_owned),
alias: Some(alias.to_string()),
proto: None,
params: Vec::new(),
};
}
let proto_node = node
.children()
.find(|n| n.has_tag_name("proto"))
.expect("command without proto");
let proto = ctype::parse_decl(proto_node);
let name = proto.name.clone();
let params = node
.children()
.filter(|n| n.has_tag_name("param"))
.map(ctype::parse_decl)
.collect();
Command {
name,
api: node.attribute("api").map(ToOwned::to_owned),
alias: None,
proto: Some(proto),
params,
}
}
fn parse_feature(node: roxmltree::Node<'_, '_>) -> Feature {
Feature {
name: node.attribute("name").unwrap().to_string(),
api: node.attribute("api").unwrap().to_string(),
apitype: node.attribute("apitype").map(ToOwned::to_owned),
depends: node.attribute("depends").map(ToOwned::to_owned),
requires: node
.children()
.filter(|n| n.has_tag_name("require"))
.map(parse_require)
.collect(),
}
}
fn parse_extension(node: roxmltree::Node<'_, '_>) -> Extension {
Extension {
name: node.attribute("name").unwrap().to_string(),
number: node.attribute("number").unwrap().parse().unwrap(),
supported: node.attribute("supported").unwrap_or("").to_string(),
depends: node.attribute("depends").map(ToOwned::to_owned),
requires: node
.children()
.filter(|n| n.has_tag_name("require"))
.map(parse_require)
.collect(),
}
}
fn parse_require(node: roxmltree::Node<'_, '_>) -> Require {
Require {
api: node.attribute("api").map(ToOwned::to_owned),
depends: node.attribute("depends").map(ToOwned::to_owned),
types: node
.children()
.filter(|n| n.has_tag_name("type"))
.filter_map(|n| n.attribute("name").map(ToOwned::to_owned))
.collect(),
commands: node
.children()
.filter(|n| n.has_tag_name("command"))
.filter_map(|n| n.attribute("name").map(ToOwned::to_owned))
.collect(),
enums: node
.children()
.filter(|n| n.has_tag_name("enum"))
.filter_map(parse_enum_entry)
.collect(),
}
}
fn parse_enum_entry(node: roxmltree::Node<'_, '_>) -> Option<EnumEntry> {
let name = node.attribute("name")?.to_string();
Some(EnumEntry {
name,
value: node.attribute("value").map(ToOwned::to_owned),
bitpos: node.attribute("bitpos").map(|v| v.parse().unwrap()),
offset: node.attribute("offset").map(|v| v.parse().unwrap()),
alias: node.attribute("alias").map(ToOwned::to_owned),
extends: node.attribute("extends").map(ToOwned::to_owned),
extnumber: node.attribute("extnumber").map(|v| v.parse().unwrap()),
dir: node.attribute("dir").map(ToOwned::to_owned),
ty: node.attribute("type").map(ToOwned::to_owned),
api: node.attribute("api").map(ToOwned::to_owned),
})
}
fn child_text(node: roxmltree::Node<'_, '_>, tag: &str) -> Option<String> {
node.children()
.find(|n| n.has_tag_name(tag))
.and_then(|n| n.text())
.map(str::trim)
.filter(|s| !s.is_empty())
.map(ToOwned::to_owned)
}
fn c_text(node: roxmltree::Node<'_, '_>) -> String {
let mut out = String::new();
for child in node.children() {
if child.is_text() {
out.push_str(child.text().unwrap());
} else if child.is_element() && !child.has_tag_name("comment") {
out.push_str(child.text().unwrap_or(""));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn type_def(xml: &str) -> TypeDef {
let doc = roxmltree::Document::parse(xml).unwrap();
parse_type(doc.root_element())
}
#[test]
fn parses_pointer_basetype_typedef() {
let ty = type_def(
"<type category=\"basetype\">typedef <type>void</type>* <name>VkRemoteAddressNV</name>;</type>",
);
let decl = ty.underlying_decl.as_ref().unwrap();
assert_eq!(decl.base, "void");
assert_eq!(decl.pointer_const, vec![false]);
}
#[test]
fn keeps_define_text() {
let ty =
type_def("<type category=\"define\">#define <name>VK_HEADER_VERSION</name> 349</type>");
assert_eq!(
ty.define_text.as_deref(),
Some("#define VK_HEADER_VERSION 349")
);
}
#[test]
fn parses_bitmask_bitvalues() {
let ty = type_def(
"<type bitvalues=\"VkAccessFlagBits2\" category=\"bitmask\">typedef <type>VkFlags64</type> <name>VkAccessFlags2</name>;</type>",
);
assert_eq!(ty.bitvalues.as_deref(), Some("VkAccessFlagBits2"));
assert_eq!(ty.underlying_type.as_deref(), Some("VkFlags64"));
}
}