uxie 0.5.6

Data fetching library for Pokemon Gen 4 romhacking - map headers, C parsing, and more
Documentation
use regex::Regex;
use std::sync::LazyLock;

static RE_ENUM: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"enum\s*([A-Za-z_][A-Za-z0-9_]*)?\s*\{([^}]+)\}").unwrap());

static RE_VARIANT: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"([A-Za-z_][A-Za-z0-9_]*)(?:\s*=\s*([^,\n]+))?").unwrap());

#[derive(Debug, Clone, PartialEq, Eq)]

pub struct CEnumVariant {
    pub name: String,
    pub value: Option<i64>,
    pub raw_value: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CEnum {
    pub name: Option<String>,
    pub variants: Vec<CEnumVariant>,
}

impl CEnum {
    pub fn get_value(&self, name: &str) -> Option<i64> {
        let mut current_value: i64 = 0;
        for variant in &self.variants {
            if let Some(v) = variant.value {
                current_value = v;
            }
            if variant.name == name {
                return Some(current_value);
            }
            current_value += 1;
        }
        None
    }

    pub fn get_name(&self, value: i64) -> Option<&str> {
        let mut current_value: i64 = 0;
        for variant in &self.variants {
            if let Some(v) = variant.value {
                current_value = v;
            }
            if current_value == value {
                return Some(&variant.name);
            }
            current_value += 1;
        }
        None
    }
}

pub fn parse_enums(source: &str) -> Vec<CEnum> {
    let mut enums = Vec::new();
    for caps in RE_ENUM.captures_iter(source) {
        let name = caps.get(1).map(|m| m.as_str().to_string());
        let body = &caps[2];

        let mut variants = Vec::new();
        for line in body.lines() {
            let line = line.trim().trim_end_matches(',');
            if line.is_empty() || line.starts_with("//") {
                continue;
            }

            if let Some(var_caps) = RE_VARIANT.captures(line) {
                let var_name = var_caps[1].to_string();
                let raw_value = var_caps.get(2).map(|m| m.as_str().trim().to_string());
                let value = raw_value.as_ref().and_then(|v| {
                    if v.starts_with("0x") || v.starts_with("0X") {
                        i64::from_str_radix(&v[2..], 16).ok()
                    } else {
                        v.parse().ok()
                    }
                });
                variants.push(CEnumVariant {
                    name: var_name,
                    value,
                    raw_value,
                });
            }
        }
        enums.push(CEnum { name, variants });
    }
    enums
}

pub fn parse_enum(source: &str) -> Option<CEnum> {
    parse_enums(source).into_iter().next()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_enum() {
        let source = r"
enum MapHeader {
    MAP_HEADER_EVERYWHERE = 0,
    MAP_HEADER_NOTHING,
    MAP_HEADER_UNDERGROUND,
    MAP_HEADER_JUBILIFE_CITY,
    MAP_HEADER_COUNT = 593,
}
        ";

        let e = parse_enum(source).unwrap();
        assert_eq!(e.name, Some("MapHeader".to_string()));
        assert_eq!(e.get_value("MAP_HEADER_EVERYWHERE"), Some(0));
        assert_eq!(e.get_value("MAP_HEADER_NOTHING"), Some(1));
        assert_eq!(e.get_value("MAP_HEADER_UNDERGROUND"), Some(2));
        assert_eq!(e.get_value("MAP_HEADER_COUNT"), Some(593));
    }
}