mimispec 0.2.0

A high-density intent description language for human-AI collaboration
Documentation
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use crate::ast::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolKind {
    Module,
    TypeDef,
    Func,
    Flow,
    Ui,
}

impl std::fmt::Display for SymbolKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SymbolKind::Module => write!(f, "module"),
            SymbolKind::TypeDef => write!(f, "type"),
            SymbolKind::Func => write!(f, "func"),
            SymbolKind::Flow => write!(f, "flow"),
            SymbolKind::Ui => write!(f, "ui"),
        }
    }
}

#[derive(Debug, Clone)]
pub struct SymbolEntry {
    pub name: String,
    pub kind: SymbolKind,
    pub file: PathBuf,
}

#[derive(Debug, Clone)]
pub struct SymbolConflict {
    pub name: String,
    pub entries: Vec<SymbolEntry>,
}

pub struct SymbolTable {
    entries: HashMap<String, Vec<SymbolEntry>>,
}

impl SymbolTable {
    pub fn build(files: &HashMap<PathBuf, Arc<File>>) -> Self {
        let mut entries: HashMap<String, Vec<SymbolEntry>> = HashMap::new();

        for (file_path, file) in files {
            Self::collect_fragments(&file.fragments, file_path, &mut entries);
        }

        Self { entries }
    }

    pub fn lookup(&self, name: &str) -> &[SymbolEntry] {
        self.entries
            .get(name)
            .map(|v| v.as_slice())
            .unwrap_or(&[])
    }

    pub fn all_names(&self) -> Vec<&str> {
        let mut names: Vec<&str> = self.entries.keys().map(|s| s.as_str()).collect();
        names.sort();
        names
    }

    pub fn conflicts(&self) -> Vec<SymbolConflict> {
        self.entries
            .iter()
            .filter(|(_, entries)| entries.len() > 1)
            .map(|(name, entries)| SymbolConflict {
                name: name.clone(),
                entries: entries.clone(),
            })
            .collect()
    }

    pub fn has_conflicts(&self) -> bool {
        self.entries.values().any(|v| v.len() > 1)
    }

    fn collect_fragments(
        fragments: &[Fragment],
        file_path: &Path,
        entries: &mut HashMap<String, Vec<SymbolEntry>>,
    ) {
        for fragment in fragments {
            match fragment {
                Fragment::Module { module } => {
                    Self::insert_entry(entries, &module.name.name, SymbolKind::Module, file_path);
                    Self::collect_fragments(&module.items, file_path, entries);
                }
                Fragment::TypeDef { typedef } => {
                    Self::insert_entry(entries, &typedef.name.name, SymbolKind::TypeDef, file_path);
                }
                Fragment::Func { func } => {
                    Self::insert_entry(entries, &func.name.name, SymbolKind::Func, file_path);
                }
                Fragment::Flow { flow } => {
                    Self::insert_entry(entries, &flow.name.name, SymbolKind::Flow, file_path);
                }
                Fragment::Ui { ui } => {
                    Self::insert_entry(entries, &ui.name.name, SymbolKind::Ui, file_path);
                }
                _ => {}
            }
        }
    }

    fn insert_entry(
        entries: &mut HashMap<String, Vec<SymbolEntry>>,
        name: &str,
        kind: SymbolKind,
        file: &Path,
    ) {
        entries
            .entry(name.to_string())
            .or_default()
            .push(SymbolEntry {
                name: name.to_string(),
                kind,
                file: file.to_path_buf(),
            });
    }
}