Skip to main content

sysml_core/
parser.rs

1use std::path::Path;
2
3use nomograph_core::traits::Parser;
4use nomograph_core::types::{Diagnostic, ParseResult};
5
6use crate::element::SysmlElement;
7use crate::relationship::SysmlRelationship;
8use crate::walker::{collect_parse_errors, Walker};
9
10pub struct SysmlParser;
11
12impl SysmlParser {
13    pub fn new() -> Self {
14        Self
15    }
16}
17
18impl Default for SysmlParser {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl Parser for SysmlParser {
25    type Elem = SysmlElement;
26    type Rel = SysmlRelationship;
27    type Error = nomograph_core::CoreError;
28
29    fn parse(
30        &self,
31        source: &str,
32        path: &Path,
33    ) -> Result<ParseResult<Self::Elem, Self::Rel>, Self::Error> {
34        let mut ts_parser = tree_sitter::Parser::new();
35        ts_parser
36            .set_language(&tree_sitter_sysml::LANGUAGE.into())
37            .map_err(|e| nomograph_core::CoreError::Parse(e.to_string()))?;
38
39        let tree = ts_parser
40            .parse(source, None)
41            .ok_or_else(|| nomograph_core::CoreError::Parse("Parser returned None".to_string()))?;
42
43        let root = tree.root_node();
44
45        let mut diagnostics = Vec::new();
46        collect_parse_errors(root, source, &mut diagnostics);
47
48        let mut walker = Walker::new(source, path.to_path_buf());
49        walker.walk_root(root);
50
51        Ok(ParseResult {
52            elements: walker.elements,
53            relationships: walker.relationships,
54            diagnostics,
55        })
56    }
57
58    fn validate(&self, source: &str) -> Vec<Diagnostic> {
59        let mut ts_parser = tree_sitter::Parser::new();
60        if ts_parser
61            .set_language(&tree_sitter_sysml::LANGUAGE.into())
62            .is_err()
63        {
64            return vec![Diagnostic {
65                severity: nomograph_core::types::Severity::Error,
66                message: "Failed to initialize parser".to_string(),
67                span: nomograph_core::types::Span {
68                    start_line: 0,
69                    start_col: 0,
70                    end_line: 0,
71                    end_col: 0,
72                },
73            }];
74        }
75
76        let Some(tree) = ts_parser.parse(source, None) else {
77            return vec![Diagnostic {
78                severity: nomograph_core::types::Severity::Error,
79                message: "Parser returned None".to_string(),
80                span: nomograph_core::types::Span {
81                    start_line: 0,
82                    start_col: 0,
83                    end_line: 0,
84                    end_col: 0,
85                },
86            }];
87        };
88
89        let mut diagnostics = Vec::new();
90        collect_parse_errors(tree.root_node(), source, &mut diagnostics);
91        diagnostics
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use nomograph_core::types::Severity;
99    use std::path::PathBuf;
100
101    fn test_path() -> PathBuf {
102        PathBuf::from("test.sysml")
103    }
104
105    fn eve_fixture(name: &str) -> String {
106        let base = env!("CARGO_MANIFEST_DIR");
107        let path = format!("{}/../../tests/fixtures/eve/DomainModel/{}", base, name);
108        std::fs::read_to_string(&path)
109            .unwrap_or_else(|e| panic!("Failed to read fixture {}: {}", path, e))
110    }
111
112    #[test]
113    fn test_parse_simple_package() {
114        let parser = SysmlParser::new();
115        let source = "package Test { part def Engine; }";
116        let result = parser.parse(source, &test_path()).unwrap();
117
118        assert!(!result.elements.is_empty());
119        let names: Vec<&str> = result
120            .elements
121            .iter()
122            .map(|e| e.qualified_name.as_str())
123            .collect();
124        assert!(names.contains(&"Test"), "missing Test package");
125        assert!(names.contains(&"Test::Engine"), "missing Test::Engine");
126    }
127
128    #[test]
129    fn test_parse_element_kinds() {
130        let parser = SysmlParser::new();
131        let source = "package Test { part def Engine; }";
132        let result = parser.parse(source, &test_path()).unwrap();
133
134        let engine = result
135            .elements
136            .iter()
137            .find(|e| e.qualified_name == "Test::Engine")
138            .unwrap();
139        assert_eq!(engine.kind, "part_definition");
140    }
141
142    #[test]
143    fn test_parse_produces_relationships() {
144        let parser = SysmlParser::new();
145        let source = "package Test { part def Engine; part engine : Engine; }";
146        let result = parser.parse(source, &test_path()).unwrap();
147
148        assert!(!result.relationships.is_empty());
149        let has_typed_by = result.relationships.iter().any(|r| r.kind == "TypedBy");
150        assert!(has_typed_by, "should have TypedBy relationship");
151    }
152
153    #[test]
154    fn test_validate_valid_sysml() {
155        let parser = SysmlParser::new();
156        let source = "package Test { part def Engine; }";
157        let diagnostics = parser.validate(source);
158
159        let errors: Vec<_> = diagnostics
160            .iter()
161            .filter(|d| d.severity == Severity::Error)
162            .collect();
163        assert!(
164            errors.is_empty(),
165            "valid SysML should have no errors, got: {:?}",
166            errors
167        );
168    }
169
170    #[test]
171    fn test_validate_invalid_sysml() {
172        let parser = SysmlParser::new();
173        let source = "this is not sysml {{{";
174        let diagnostics = parser.validate(source);
175
176        let has_errors = diagnostics.iter().any(|d| d.severity == Severity::Error);
177        assert!(has_errors, "invalid SysML should have errors");
178    }
179
180    #[test]
181    fn test_parse_eve_mining_frigate() {
182        let parser = SysmlParser::new();
183        let source = eve_fixture("MiningFrigate.sysml");
184        let path = PathBuf::from("MiningFrigate.sysml");
185        let result = parser.parse(&source, &path).unwrap();
186
187        assert!(
188            !result.elements.is_empty(),
189            "should extract elements from MiningFrigate.sysml"
190        );
191        assert!(
192            !result.relationships.is_empty(),
193            "should extract relationships"
194        );
195    }
196
197    #[test]
198    fn test_parse_eve_requirements() {
199        let parser = SysmlParser::new();
200        let source = eve_fixture("MiningFrigateRequirements.sysml");
201        let path = PathBuf::from("MiningFrigateRequirements.sysml");
202        let result = parser.parse(&source, &path).unwrap();
203
204        assert!(!result.elements.is_empty());
205        let req_elements: Vec<_> = result
206            .elements
207            .iter()
208            .filter(|e| e.kind.contains("requirement"))
209            .collect();
210        assert!(!req_elements.is_empty(), "should have requirement elements");
211    }
212
213    #[test]
214    fn test_parse_file_path_stored() {
215        let parser = SysmlParser::new();
216        let source = "package Test { part def Engine; }";
217        let path = PathBuf::from("my/test.sysml");
218        let result = parser.parse(source, &path).unwrap();
219
220        for elem in &result.elements {
221            assert_eq!(elem.file_path, path);
222        }
223    }
224}