Theme

alex/volcanic

Automated vulkan bindings

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

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