aboutsummaryrefslogtreecommitdiff
path: root/mingling_pathf/src/pattern_analyzer.rs
blob: cb98a5fc555d5e71db8ee474a7fde74cf79adf6d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::collections::HashSet;
use std::path::Path;

use crate::error::MinglingPathfinderError;

/// Top-level convenience function: creates a default PatternAnalyzer with all built-in patterns pre-registered
pub fn init() -> PatternAnalyzer {
    let mut analyzer = PatternAnalyzer::new();
    macro_rules! __register {
        ( $($pattern:ident),* $(,)? ) => {
            $(
                analyzer.add_pattern(crate::patterns::$pattern);
            )*
        };
    }

    __register![
        BasicStructPattern,
    ];

    analyzer
}

/// A single analysis item representing a parseable importable/referenceable item from code
#[derive(Debug, Clone)]
pub struct AnalyzeItem {
    /// The module path to which the item belongs, e.g. `"std::collections"`; empty string `""` if the item is in the root module
    pub module: String,
    /// The name of the item itself, e.g. `"HashMap"`, `"AnalyzeResult"`, etc.
    pub item_name: String,
}

/// Collection of analysis results
#[derive(Debug)]
pub struct AnalyzeResult {
    items: Vec<AnalyzeItem>,
}

impl AnalyzeResult {
    /// Creates an empty `AnalyzeResult` instance
    pub fn new() -> Self {
        Self { items: Vec::new() }
    }

    /// Formats all items into a set of strings in the format `"::{module_path}::{item_name}"`
    pub fn into_formatted(self) -> HashSet<String> {
        self.items
            .into_iter()
            .map(|item| {
                if item.module.is_empty() {
                    format!("::{}", item.item_name)
                } else {
                    format!("::{}::{}", item.module, item.item_name)
                }
            })
            .collect()
    }
}

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

/// Extension point trait — one independent implementation per syntax kind
pub trait AnalyzePattern {
    /// Quickly determine whether the file content contains an analyzable item
    fn contains(&self, content: &str) -> bool;

    /// Analyze the content and return all found AnalyzeItems
    fn analyze(&self, content: &str) -> Vec<AnalyzeItem>;
}

/// A pattern analyzer that registers and runs multiple `AnalyzePattern` instances to parse
/// referenceable items from code.
///
/// Internally maintains a `Vec<Box<dyn AnalyzePattern>>` for dynamic dispatch, supporting
/// runtime registration of custom patterns. The `Default` derive provides an empty analyzer
/// instance.
///
/// # Fields
/// - `patterns`: A list of registered analysis patterns, each responsible for detecting and
///   extracting a specific type of analyzable item (e.g., structs, functions).
#[derive(Default)]
pub struct PatternAnalyzer {
    /// A list of registered analysis patterns, each responsible for detecting and extracting
    /// a specific type of analyzable item.
    patterns: Vec<Box<dyn AnalyzePattern>>,
}

impl PatternAnalyzer {
    /// Creates a new `PatternAnalyzer` instance.
    ///
    /// Internally calls `Default::default()` directly, initially containing no registered patterns.
    pub fn new() -> Self {
        Self::default()
    }

    /// Registers an analysis pattern with the current analyzer.
    ///
    /// The pattern is wrapped in a `Box` and stored in the internal pattern list,
    /// and will be used to scan and analyze file content in subsequent calls to `analyze_file`.
    ///
    /// # Parameters
    /// - `pattern` —— A type instance implementing the `AnalyzePattern` trait,
    ///   responsible for detecting and extracting a specific syntactic structure (e.g., structs, functions).
    pub fn add_pattern(&mut self, pattern: impl AnalyzePattern + 'static) {
        self.patterns.push(Box::new(pattern));
    }

    /// Analyzes a single file and returns a formatted set of strings.
    ///
    /// This method reads the content of the file at the specified path, then uses each registered
    /// pattern to analyze the content in turn. Only patterns whose `contains` method returns `true`
    /// will trigger the subsequent `analyze` call. All extracted `AnalyzeItem`s are merged and
    /// converted into a formatted `HashSet<String>`.
    ///
    /// # Parameters
    /// - `path` —— The path to the file to analyze, supporting any type that implements `AsRef<Path>`.
    ///
    /// # Returns
    /// - `Ok(HashSet<String>)` —— On success, returns a formatted set of strings, each in the form `"::module_path::item_name"`.
    /// - `Err(MinglingPathfinderError)` —— If the file cannot be read, returns the corresponding I/O error wrapper.
    pub fn analyze_file(&self, path: impl AsRef<Path>) -> Result<HashSet<String>, MinglingPathfinderError> {
        let path = path.as_ref();
        let content = std::fs::read_to_string(path)?;

        let mut all_items = Vec::new();
        for pattern in &self.patterns {
            if pattern.contains(&content) {
                let items = pattern.analyze(&content);
                all_items.extend(items);
            }
        }

        let result = AnalyzeResult { items: all_items };
        Ok(result.into_formatted())
    }
}