oxihuman-core 0.1.2

Core data structures, algorithms, and asset management for OxiHuman
Documentation
// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
// SPDX-License-Identifier: Apache-2.0
#![allow(dead_code)]

//! Source map: maps generated byte offsets to original (file, line, col) positions.

/// A single source mapping entry.
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct SourceMapping {
    pub generated_offset: u32,
    pub source_file: String,
    pub source_line: u32,
    pub source_col: u32,
    pub name: Option<String>,
}

/// Source map holding a list of mappings sorted by generated offset.
#[allow(dead_code)]
pub struct SourceMap {
    mappings: Vec<SourceMapping>,
    source_files: Vec<String>,
}

#[allow(dead_code)]
impl SourceMap {
    pub fn new() -> Self {
        Self {
            mappings: Vec::new(),
            source_files: Vec::new(),
        }
    }

    /// Add a mapping entry.
    pub fn add_mapping(
        &mut self,
        generated_offset: u32,
        source_file: &str,
        source_line: u32,
        source_col: u32,
        name: Option<&str>,
    ) {
        if !self.source_files.contains(&source_file.to_string()) {
            self.source_files.push(source_file.to_string());
        }
        self.mappings.push(SourceMapping {
            generated_offset,
            source_file: source_file.to_string(),
            source_line,
            source_col,
            name: name.map(|s| s.to_string()),
        });
        self.mappings.sort_unstable_by_key(|m| m.generated_offset);
    }

    /// Look up the source position for a generated offset.
    pub fn lookup(&self, generated_offset: u32) -> Option<&SourceMapping> {
        if self.mappings.is_empty() {
            return None;
        }
        match self
            .mappings
            .binary_search_by_key(&generated_offset, |m| m.generated_offset)
        {
            Ok(i) => Some(&self.mappings[i]),
            Err(i) => {
                if i == 0 {
                    None
                } else {
                    Some(&self.mappings[i - 1])
                }
            }
        }
    }

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

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

    pub fn source_files(&self) -> &[String] {
        &self.source_files
    }

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

    /// All mappings for a specific source file.
    pub fn mappings_for_file(&self, file: &str) -> Vec<&SourceMapping> {
        self.mappings
            .iter()
            .filter(|m| m.source_file == file)
            .collect()
    }

    pub fn clear(&mut self) {
        self.mappings.clear();
        self.source_files.clear();
    }
}

impl Default for SourceMap {
    fn default() -> Self {
        Self::new()
    }
}

pub fn new_source_map() -> SourceMap {
    SourceMap::new()
}

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

    #[test]
    fn add_and_lookup_exact() {
        let mut sm = new_source_map();
        sm.add_mapping(100, "main.rs", 10, 5, None);
        let m = sm.lookup(100).expect("should succeed");
        assert_eq!(m.source_line, 10);
    }

    #[test]
    fn lookup_between_entries() {
        let mut sm = new_source_map();
        sm.add_mapping(0, "a.rs", 1, 0, None);
        sm.add_mapping(50, "a.rs", 5, 0, None);
        let m = sm.lookup(25).expect("should succeed");
        assert_eq!(m.source_line, 1);
    }

    #[test]
    fn lookup_before_first_returns_none() {
        let mut sm = new_source_map();
        sm.add_mapping(10, "a.rs", 1, 0, None);
        assert!(sm.lookup(0).is_none());
    }

    #[test]
    fn source_file_deduplication() {
        let mut sm = new_source_map();
        sm.add_mapping(0, "a.rs", 1, 0, None);
        sm.add_mapping(1, "a.rs", 2, 0, None);
        assert_eq!(sm.source_file_count(), 1);
    }

    #[test]
    fn multiple_source_files() {
        let mut sm = new_source_map();
        sm.add_mapping(0, "a.rs", 1, 0, None);
        sm.add_mapping(10, "b.rs", 1, 0, None);
        assert_eq!(sm.source_file_count(), 2);
    }

    #[test]
    fn mappings_for_file() {
        let mut sm = new_source_map();
        sm.add_mapping(0, "a.rs", 1, 0, None);
        sm.add_mapping(10, "b.rs", 1, 0, None);
        assert_eq!(sm.mappings_for_file("a.rs").len(), 1);
    }

    #[test]
    fn named_mapping() {
        let mut sm = new_source_map();
        sm.add_mapping(0, "a.rs", 1, 0, Some("main"));
        let m = sm.lookup(0).expect("should succeed");
        assert_eq!(m.name.as_deref(), Some("main"));
    }

    #[test]
    fn empty_map_is_empty() {
        let sm = new_source_map();
        assert!(sm.is_empty());
    }

    #[test]
    fn clear() {
        let mut sm = new_source_map();
        sm.add_mapping(0, "a.rs", 1, 0, None);
        sm.clear();
        assert!(sm.is_empty());
    }

    #[test]
    fn mapping_count() {
        let mut sm = new_source_map();
        sm.add_mapping(0, "a.rs", 1, 0, None);
        sm.add_mapping(5, "a.rs", 2, 0, None);
        assert_eq!(sm.mapping_count(), 2);
    }
}