Skip to main content

cgx_engine/
parser.rs

1use std::collections::HashMap;
2
3use crate::walker::{Language, SourceFile};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
6pub enum NodeKind {
7    File,
8    Function,
9    Class,
10    Variable,
11    Type,
12    Module,
13    Author,
14}
15
16impl NodeKind {
17    pub fn as_str(&self) -> &'static str {
18        match self {
19            NodeKind::File => "File",
20            NodeKind::Function => "Function",
21            NodeKind::Class => "Class",
22            NodeKind::Variable => "Variable",
23            NodeKind::Type => "Type",
24            NodeKind::Module => "Module",
25            NodeKind::Author => "Author",
26        }
27    }
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
31#[serde(rename_all = "UPPERCASE")]
32pub enum EdgeKind {
33    Calls,
34    Imports,
35    Inherits,
36    Exports,
37    CoChanges,
38    Owns,
39    DependsOn,
40}
41
42impl EdgeKind {
43    pub fn as_str(&self) -> &'static str {
44        match self {
45            EdgeKind::Calls => "CALLS",
46            EdgeKind::Imports => "IMPORTS",
47            EdgeKind::Inherits => "INHERITS",
48            EdgeKind::Exports => "EXPORTS",
49            EdgeKind::CoChanges => "CO_CHANGES",
50            EdgeKind::Owns => "OWNS",
51            EdgeKind::DependsOn => "DEPENDS_ON",
52        }
53    }
54}
55
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub struct NodeDef {
58    pub id: String,
59    pub kind: NodeKind,
60    pub name: String,
61    pub path: String,
62    pub line_start: u32,
63    pub line_end: u32,
64    #[serde(default)]
65    pub metadata: serde_json::Value,
66}
67
68impl Default for NodeDef {
69    fn default() -> Self {
70        Self {
71            id: String::new(),
72            kind: NodeKind::File,
73            name: String::new(),
74            path: String::new(),
75            line_start: 0,
76            line_end: 0,
77            metadata: serde_json::Value::Null,
78        }
79    }
80}
81
82#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
83pub struct EdgeDef {
84    pub src: String,
85    pub dst: String,
86    pub kind: EdgeKind,
87    #[serde(default = "default_edge_weight")]
88    pub weight: f64,
89    #[serde(default = "default_edge_weight")]
90    pub confidence: f64,
91}
92
93impl Default for EdgeDef {
94    fn default() -> Self {
95        Self {
96            src: String::new(),
97            dst: String::new(),
98            kind: EdgeKind::Calls,
99            weight: 1.0,
100            confidence: 1.0,
101        }
102    }
103}
104
105fn default_edge_weight() -> f64 {
106    1.0
107}
108
109#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
110pub struct ParseResult {
111    pub nodes: Vec<NodeDef>,
112    pub edges: Vec<EdgeDef>,
113}
114
115impl ParseResult {
116    pub fn new() -> Self {
117        Self {
118            nodes: Vec::new(),
119            edges: Vec::new(),
120        }
121    }
122}
123
124impl Default for ParseResult {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130pub trait LanguageParser: Send + Sync {
131    fn extensions(&self) -> &[&str];
132    fn extract(&self, file: &SourceFile) -> anyhow::Result<ParseResult>;
133}
134
135pub struct ParserRegistry {
136    parsers: HashMap<Language, Box<dyn LanguageParser>>,
137}
138
139impl ParserRegistry {
140    pub fn new() -> Self {
141        let mut parsers: HashMap<Language, Box<dyn LanguageParser>> = HashMap::new();
142
143        parsers.insert(
144            Language::TypeScript,
145            Box::new(super::parsers::ts::TypeScriptParser::new()),
146        );
147        parsers.insert(
148            Language::JavaScript,
149            Box::new(super::parsers::ts::TypeScriptParser::new()),
150        );
151        parsers.insert(
152            Language::Python,
153            Box::new(super::parsers::py::PythonParser::new()),
154        );
155        parsers.insert(
156            Language::Rust,
157            Box::new(super::parsers::rust::RustParser::new()),
158        );
159        parsers.insert(Language::Go, Box::new(super::parsers::go::GoParser::new()));
160        parsers.insert(
161            Language::Java,
162            Box::new(super::parsers::java::JavaParser::new()),
163        );
164        parsers.insert(
165            Language::CSharp,
166            Box::new(super::parsers::java::JavaParser::new()),
167        );
168        parsers.insert(
169            Language::Php,
170            Box::new(super::parsers::php::PhpParser::new()),
171        );
172
173        Self { parsers }
174    }
175
176    pub fn parse(&self, file: &SourceFile) -> anyhow::Result<ParseResult> {
177        if let Some(parser) = self.parsers.get(&file.language) {
178            parser.extract(file)
179        } else {
180            Ok(ParseResult::new())
181        }
182    }
183
184    pub fn parse_all(&self, files: &[SourceFile]) -> Vec<ParseResult> {
185        use rayon::prelude::*;
186        files
187            .par_iter()
188            .map(|file| {
189                self.parse(file).unwrap_or_else(|e| {
190                    tracing::warn!("Parse error in {}: {}", file.relative_path, e);
191                    ParseResult::new()
192                })
193            })
194            .collect()
195    }
196}
197
198impl Default for ParserRegistry {
199    fn default() -> Self {
200        Self::new()
201    }
202}