alex/volcanic
Automated vulkan bindings
BRANCH main
SNAPSHOT Improved loading api and made ready for publish
DOWNLOAD
root codegen src ctype.rs
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Decl {
pub name: String,
pub api: Option<String>,
pub base: String,
pub base_const: bool,
pub pointer_const: Vec<bool>,
pub arrays: Vec<String>,
pub bit_width: Option<u32>,
}
pub fn parse_decl(node: roxmltree::Node<'_, '_>) -> Decl {
let mut before_type = String::new();
let mut after_type = String::new();
let mut after_name = String::new();
let mut base = None;
let mut name = None;
let mut state = State::BeforeType;
for child in node.children() {
if child.is_text() {
match state {
State::BeforeType => before_type.push_str(child.text().unwrap()),
State::AfterType => after_type.push_str(child.text().unwrap()),
State::AfterName => after_name.push_str(child.text().unwrap()),
}
} else if child.has_tag_name("type") {
base = Some(child.text().unwrap().trim().to_string());
state = State::AfterType;
} else if child.has_tag_name("name") {
name = Some(child.text().unwrap().trim().to_string());
state = State::AfterName;
} else if child.has_tag_name("enum") {
if state == State::AfterName {
after_name.push_str(child.text().unwrap_or(""));
}
} else if !child.has_tag_name("comment") {
match state {
State::BeforeType => before_type.push_str(child.text().unwrap_or("")),
State::AfterType => after_type.push_str(child.text().unwrap_or("")),
State::AfterName => after_name.push_str(child.text().unwrap_or("")),
}
}
}
let base = base.expect("declaration without <type>");
let name = name.expect("declaration without <name>");
let base_const = before_type.split_whitespace().any(|part| part == "const");
let pointer_const = pointer_levels(base_const, &after_type);
let arrays = parse_arrays(&after_name);
let bit_width = parse_bit_width(&after_name);
Decl {
name,
api: node.attribute("api").map(ToOwned::to_owned),
base,
base_const,
pointer_const,
arrays,
bit_width,
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum State {
BeforeType,
AfterType,
AfterName,
}
fn pointer_levels(base_const: bool, after_type: &str) -> Vec<bool> {
let mut levels = Vec::new();
let mut pointee_const = base_const;
for segment in after_type.split('*').skip(1) {
levels.push(pointee_const);
pointee_const = segment.split_whitespace().any(|part| part == "const");
}
levels
}
fn parse_arrays(after_name: &str) -> Vec<String> {
let mut arrays = Vec::new();
let mut rest = after_name;
while let Some((_, suffix)) = rest.split_once('[') {
let (dimension, suffix) = suffix.split_once(']').expect("unclosed array dimension");
assert!(!dimension.trim().is_empty(), "empty array dimension");
arrays.push(dimension.trim().to_string());
rest = suffix;
}
arrays
}
fn parse_bit_width(after_name: &str) -> Option<u32> {
let (_, rhs) = after_name.split_once(':')?;
Some(rhs.trim().parse().expect("invalid bitfield width"))
}
#[cfg(test)]
mod tests {
use super::*;
fn decl(xml: &str) -> Decl {
let doc = roxmltree::Document::parse(xml).unwrap();
parse_decl(doc.root_element())
}
#[test]
fn rejects_malformed_declarations() {
for suffix in ["[4", "[]", ":", ": nope"] {
let xml = format!("<member><type>uint32_t</type> <name>value</name>{suffix}</member>");
assert!(
std::panic::catch_unwind(|| decl(&xml)).is_err(),
"accepted {suffix:?}"
);
}
}
#[test]
fn parses_const_pointer_to_const_pointer() {
let d = decl("<param>const <type>char</type>* const* <name>names</name></param>");
assert_eq!(d.base, "char");
assert_eq!(d.pointer_const, vec![true, true]);
}
#[test]
fn parses_arrays_and_bitfields() {
let d = decl(
"<member><type>uint8_t</type> <name>uuid</name>[<enum>VK_UUID_SIZE</enum>]</member>",
);
assert_eq!(d.arrays, vec!["VK_UUID_SIZE"]);
let d = decl("<member><type>uint32_t</type> <name>flag</name> : 1</member>");
assert_eq!(d.bit_width, Some(1));
}
}