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(
160            Language::Go,
161            Box::new(super::parsers::go::GoParser::new()),
162        );
163        parsers.insert(
164            Language::Java,
165            Box::new(super::parsers::java::JavaParser::new()),
166        );
167        parsers.insert(
168            Language::CSharp,
169            Box::new(super::parsers::java::JavaParser::new()),
170        );
171        parsers.insert(
172            Language::Php,
173            Box::new(super::parsers::php::PhpParser::new()),
174        );
175
176        Self { parsers }
177    }
178
179    pub fn parse(&self, file: &SourceFile) -> anyhow::Result<ParseResult> {
180        if let Some(parser) = self.parsers.get(&file.language) {
181            parser.extract(file)
182        } else {
183            Ok(ParseResult::new())
184        }
185    }
186
187    pub fn parse_all(&self, files: &[SourceFile]) -> Vec<ParseResult> {
188        use rayon::prelude::*;
189        files
190            .par_iter()
191            .map(|file| self.parse(file).unwrap_or_else(|e| {
192                tracing::warn!("Parse error in {}: {}", file.relative_path, e);
193                ParseResult::new()
194            }))
195            .collect()
196    }
197}
198
199impl Default for ParserRegistry {
200    fn default() -> Self {
201        Self::new()
202    }
203}