Theme

alex/volcanic

Automated vulkan bindings

BRANCH main
main
SNAPSHOT Improved loading api and made ready for publish
DOWNLOAD
Download file

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"));
    }
}