pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
/// Column-oriented storage for SIMD operations
pub struct ColumnStore<T> {
    data: Vec<T>,
    #[allow(dead_code)]
    capacity: usize,
}

impl<T: Clone> ColumnStore<T> {
    #[must_use]
    pub fn new(capacity: usize) -> Self {
        Self {
            data: Vec::with_capacity(capacity),
            capacity,
        }
    }

    pub fn push(&mut self, item: T) -> NodeKey {
        let key = self.data.len() as NodeKey;
        self.data.push(item);
        key
    }

    #[must_use]
    pub fn get(&self, key: NodeKey) -> Option<&T> {
        self.data.get(key as usize)
    }

    pub fn get_mut(&mut self, key: NodeKey) -> Option<&mut T> {
        self.data.get_mut(key as usize)
    }

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.data.iter()
    }

    #[must_use]
    pub fn len(&self) -> usize {
        self.data.len()
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
}

/// AST DAG structure for efficient traversal and analysis
pub struct AstDag {
    /// Columnar storage for SIMD operations
    pub nodes: ColumnStore<UnifiedAstNode>,

    /// Language-specific parsers for multi-language support
    pub parsers: LanguageParsers,

    /// Incremental update tracking
    pub dirty_nodes: roaring::RoaringBitmap,

    /// Generation counter for cache invalidation
    pub generation: std::sync::atomic::AtomicU32,
}

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

impl AstDag {
    /// Creates a new AST DAG with default configuration.
    ///
    /// Initializes an empty DAG with:
    /// - Column store with 10,000 initial node capacity
    /// - Empty roaring bitmap for dirty node tracking
    /// - Generation counter starting at 0
    /// - Default language parsers
    ///
    /// # Performance Characteristics
    ///
    /// - Memory: ~40MB initial allocation for node storage
    /// - Insertion: O(1) amortized with occasional reallocation
    /// - Lookup: O(1) by node key
    /// - Dirty tracking: O(1) insertion/removal with compressed bitmaps
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pmat::models::unified_ast::{
    ///     AstDag, UnifiedAstNode, AstKind, FunctionKind, Language
    /// };
    ///
    /// let mut dag = AstDag::new();
    ///
    /// // Initially empty
    /// assert_eq!(dag.nodes.len(), 0);
    /// assert!(dag.nodes.is_empty());
    /// assert_eq!(dag.generation(), 0);
    ///
    /// // Add a node
    /// let node = UnifiedAstNode::new(
    ///     AstKind::Function(FunctionKind::Regular),
    ///     Language::Rust
    /// );
    /// let key = dag.add_node(node);
    ///
    /// assert_eq!(dag.nodes.len(), 1);
    /// assert_eq!(dag.generation(), 1);
    /// assert!(dag.dirty_nodes().any(|k| k == key));
    /// ```
    #[must_use]
    pub fn new() -> Self {
        Self {
            nodes: ColumnStore::new(10000), // Initial capacity
            parsers: LanguageParsers::default(),
            dirty_nodes: roaring::RoaringBitmap::new(),
            generation: std::sync::atomic::AtomicU32::new(0),
        }
    }

    /// Adds a new node to the DAG and returns its unique key.
    ///
    /// The node is automatically marked as dirty and the generation
    /// counter is incremented for cache invalidation.
    ///
    /// # Performance
    ///
    /// - Time: O(1) amortized, O(n) worst case during reallocation
    /// - Space: Constant overhead per node
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pmat::models::unified_ast::{
    ///     AstDag, UnifiedAstNode, AstKind, FunctionKind, ClassKind, Language
    /// };
    ///
    /// let mut dag = AstDag::new();
    /// let initial_gen = dag.generation();
    ///
    /// // Add multiple nodes
    /// let func_node = UnifiedAstNode::new(
    ///     AstKind::Function(FunctionKind::Regular),
    ///     Language::Rust
    /// );
    /// let class_node = UnifiedAstNode::new(
    ///     AstKind::Class(ClassKind::Struct),
    ///     Language::Rust
    /// );
    ///
    /// let func_key = dag.add_node(func_node);
    /// let class_key = dag.add_node(class_node);
    ///
    /// // Keys are unique and sequential
    /// assert_ne!(func_key, class_key);
    /// assert_eq!(dag.nodes.len(), 2);
    ///
    /// // Generation incremented for each addition
    /// assert_eq!(dag.generation(), initial_gen + 2);
    ///
    /// // Both nodes are dirty
    /// let dirty: Vec<_> = dag.dirty_nodes().collect();
    /// assert_eq!(dirty.len(), 2);
    /// assert!(dirty.contains(&func_key));
    /// assert!(dirty.contains(&class_key));
    ///
    /// // Nodes can be retrieved by key
    /// assert!(dag.nodes.get(func_key).expect("internal error").is_function());
    /// assert!(dag.nodes.get(class_key).expect("internal error").is_type_definition());
    /// ```
    pub fn add_node(&mut self, node: UnifiedAstNode) -> NodeKey {
        let key = self.nodes.push(node);
        self.dirty_nodes.insert(key);
        self.generation
            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
        key
    }

    /// Marks a node as clean (processed) by removing it from the dirty set.
    ///
    /// This is typically called after processing a node for incremental
    /// analysis to avoid reprocessing unchanged nodes.
    ///
    /// # Performance
    ///
    /// - Time: O(1) for roaring bitmap removal
    /// - Space: No additional allocation
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pmat::models::unified_ast::{
    ///     AstDag, UnifiedAstNode, AstKind, FunctionKind, Language
    /// };
    ///
    /// let mut dag = AstDag::new();
    ///
    /// let node = UnifiedAstNode::new(
    ///     AstKind::Function(FunctionKind::Regular),
    ///     Language::Rust
    /// );
    /// let key = dag.add_node(node);
    ///
    /// // Node starts dirty
    /// assert!(dag.dirty_nodes().any(|k| k == key));
    ///
    /// // Mark as processed
    /// dag.mark_clean(key);
    ///
    /// // No longer in dirty set
    /// assert!(!dag.dirty_nodes().any(|k| k == key));
    ///
    /// // Node still exists in the DAG
    /// assert!(dag.nodes.get(key).is_some());
    /// ```
    pub fn mark_clean(&mut self, key: NodeKey) {
        self.dirty_nodes.remove(key);
    }

    /// Returns an iterator over all dirty (unprocessed) node keys.
    ///
    /// Dirty nodes are those that have been added or modified since
    /// the last processing cycle. This enables efficient incremental
    /// analysis by only processing changed nodes.
    ///
    /// # Performance
    ///
    /// - Time: O(1) to create iterator, O(k) to iterate where k = dirty count
    /// - Space: No additional allocation (streaming iterator)
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pmat::models::unified_ast::{
    ///     AstDag, UnifiedAstNode, AstKind, FunctionKind, Language
    /// };
    ///
    /// let mut dag = AstDag::new();
    ///
    /// // Initially no dirty nodes
    /// assert_eq!(dag.dirty_nodes().count(), 0);
    ///
    /// // Add some nodes
    /// let keys: Vec<_> = (0..3).map(|_| {
    ///     dag.add_node(UnifiedAstNode::new(
    ///         AstKind::Function(FunctionKind::Regular),
    ///         Language::Rust
    ///     ))
    /// }).collect();
    ///
    /// // All nodes are dirty
    /// assert_eq!(dag.dirty_nodes().count(), 3);
    ///
    /// // Process some nodes
    /// dag.mark_clean(keys[0]);
    /// dag.mark_clean(keys[2]);
    ///
    /// // Only unprocessed nodes remain dirty
    /// let dirty: Vec<_> = dag.dirty_nodes().collect();
    /// assert_eq!(dirty.len(), 1);
    /// assert_eq!(dirty[0], keys[1]);
    /// ```
    pub fn dirty_nodes(&self) -> impl Iterator<Item = NodeKey> + '_ {
        self.dirty_nodes.iter()
    }

    /// Returns the current generation number for cache invalidation.
    ///
    /// The generation number is incremented each time a node is added
    /// to the DAG, providing a monotonic cache invalidation key.
    ///
    /// # Performance
    ///
    /// - Time: O(1) atomic load with relaxed ordering
    /// - Thread-safe: Can be called from multiple threads
    ///
    /// # Examples
    ///
    /// ```rust
    /// use pmat::models::unified_ast::{
    ///     AstDag, UnifiedAstNode, AstKind, FunctionKind, Language
    /// };
    ///
    /// let mut dag = AstDag::new();
    ///
    /// // Starts at generation 0
    /// assert_eq!(dag.generation(), 0);
    ///
    /// // Generation increments with each node addition
    /// dag.add_node(UnifiedAstNode::new(
    ///     AstKind::Function(FunctionKind::Regular),
    ///     Language::Rust
    /// ));
    /// assert_eq!(dag.generation(), 1);
    ///
    /// dag.add_node(UnifiedAstNode::new(
    ///     AstKind::Function(FunctionKind::Method),
    ///     Language::TypeScript
    /// ));
    /// assert_eq!(dag.generation(), 2);
    ///
    /// // Marking clean does not change generation
    /// dag.mark_clean(0);
    /// assert_eq!(dag.generation(), 2);
    /// ```
    pub fn generation(&self) -> u32 {
        self.generation.load(std::sync::atomic::Ordering::Relaxed)
    }
}

/// Placeholder for language-specific parsers
#[derive(Default)]
pub struct LanguageParsers {
    // TRACKED: Add actual parser implementations
}