uxie 0.6.3

Data fetching library for Pokemon Gen 4 romhacking - map headers, C parsing, and more
Documentation
use crate::c_parser::defines::{parse_defines, parse_function_macros, CDefine, CFunctionMacro};
use crate::c_parser::enums::{parse_enums, CEnum};
use crate::c_parser::includes::{parse_includes, CInclude};
use dashmap::DashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;

#[derive(Debug, Clone)]
pub struct FileEntry {
    pub defines: Vec<CDefine>,
    pub enums: Vec<CEnum>,
    pub function_macros: Vec<CFunctionMacro>,
    pub includes: Vec<CInclude>,
}

#[derive(Debug, Default, Clone)]
pub struct SourceManager {
    files: Arc<DashMap<PathBuf, Arc<FileEntry>>>,
    canonical_cache: Arc<DashMap<PathBuf, PathBuf>>,
}

impl SourceManager {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn get_or_parse(&self, path: impl AsRef<Path>) -> std::io::Result<Arc<FileEntry>> {
        let path = path.as_ref();
        let canonical = self.canonicalize_strict(path)?;

        if let Some(entry) = self.files.get(&canonical) {
            return Ok(Arc::clone(&entry));
        }

        let content = std::fs::read_to_string(path).map_err(|err| {
            std::io::Error::new(
                err.kind(),
                format!(
                    "Failed to read source file {} as UTF-8: {err}",
                    path.display()
                ),
            )
        })?;
        let entry = Arc::new(FileEntry {
            defines: parse_defines(&content),
            enums: parse_enums(&content),
            function_macros: parse_function_macros(&content),
            includes: parse_includes(&content),
        });

        self.files.insert(canonical, Arc::clone(&entry));
        Ok(entry)
    }

    pub fn canonicalize(&self, path: impl AsRef<Path>) -> PathBuf {
        let path = path.as_ref();
        self.canonicalize_strict(path)
            .unwrap_or_else(|_| path.to_path_buf())
    }

    pub fn canonicalize_strict(&self, path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
        let path = path.as_ref();

        if let Some(cached) = self.canonical_cache.get(path) {
            return Ok(cached.clone());
        }

        let canonical = path.canonicalize()?;
        self.canonical_cache
            .insert(path.to_path_buf(), canonical.clone());
        Ok(canonical)
    }

    pub fn len(&self) -> usize {
        self.files.len()
    }

    pub fn is_empty(&self) -> bool {
        self.files.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::c_parser::SymbolTable;
    use std::io::Write;
    use tempfile::tempdir;

    #[test]
    fn test_recursive_loading_with_cache() {
        let dir = tempdir().unwrap();
        let sm = SourceManager::new();

        let header_path = dir.path().join("consts.h");
        let mut header = std::fs::File::create(&header_path).unwrap();
        writeln!(header, "#define GLOBAL_CONST 100").unwrap();

        let script_path = dir.path().join("script.s");
        let mut script = std::fs::File::create(&script_path).unwrap();
        writeln!(script, "#include \"consts.h\"").unwrap();
        writeln!(script, "#define LOCAL_CONST 1").unwrap();

        let mut table = SymbolTable::with_source_manager(sm.clone());
        table.load_recursive(&script_path, &[]).unwrap();

        assert_eq!(table.resolve_constant("GLOBAL_CONST"), Some(100));
        assert_eq!(table.resolve_constant("LOCAL_CONST"), Some(1));
        assert_eq!(sm.len(), 2);

        let mut table2 = SymbolTable::with_source_manager(sm.clone());
        table2.load_recursive(&script_path, &[]).unwrap();
        assert_eq!(sm.len(), 2);
    }
}