Skip to main content

sysml_core/
resolve.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::element::SysmlElement;
4use crate::relationship::SysmlRelationship;
5
6struct ImportEdge {
7    target_namespace: String,
8    wildcard: bool,
9    segments: Vec<String>,
10}
11
12fn build_namespace_map(elements: &[SysmlElement]) -> HashMap<String, String> {
13    let mut map = HashMap::new();
14    for elem in elements {
15        if !elem.qualified_name.contains("::")
16            && (elem.kind == "package_definition" || elem.kind == "library_package")
17        {
18            map.insert(
19                elem.qualified_name.clone(),
20                elem.file_path.to_string_lossy().to_string(),
21            );
22        }
23    }
24    map
25}
26
27fn build_export_table(elements: &[SysmlElement]) -> HashMap<String, Vec<(String, String)>> {
28    let mut by_file: HashMap<String, Vec<&SysmlElement>> = HashMap::new();
29    for elem in elements {
30        let file = elem.file_path.to_string_lossy().to_string();
31        by_file.entry(file).or_default().push(elem);
32    }
33
34    let mut table = HashMap::new();
35    for (file, elems) in &by_file {
36        let ns = elems
37            .iter()
38            .find(|e| {
39                !e.qualified_name.contains("::")
40                    && (e.kind == "package_definition" || e.kind == "library_package")
41            })
42            .map(|e| e.qualified_name.as_str())
43            .unwrap_or("");
44
45        let mut exported = Vec::new();
46        for elem in elems {
47            let qname = &elem.qualified_name;
48            let short = qname.rsplit("::").next().unwrap_or(qname);
49            let is_top_level = if ns.is_empty() {
50                !qname.contains("::")
51            } else {
52                let prefix = format!("{}::", ns);
53                qname.starts_with(&prefix) && !qname[prefix.len()..].contains("::")
54            };
55            if is_top_level && qname.contains("::") {
56                exported.push((short.to_string(), qname.clone()));
57            }
58        }
59        table.insert(file.clone(), exported);
60    }
61    table
62}
63
64fn build_import_graph(relationships: &[SysmlRelationship]) -> HashMap<String, Vec<ImportEdge>> {
65    let mut graph: HashMap<String, Vec<ImportEdge>> = HashMap::new();
66    for rel in relationships {
67        if rel.kind.eq_ignore_ascii_case("import") {
68            if let Some(edge) = parse_import_target(&rel.target) {
69                let file = rel.file_path.to_string_lossy().to_string();
70                graph.entry(file).or_default().push(edge);
71            }
72        }
73    }
74    graph
75}
76
77fn parse_import_target(target: &str) -> Option<ImportEdge> {
78    if target.is_empty() {
79        return None;
80    }
81    let wildcard = target.ends_with("::*");
82    let path = if wildcard {
83        &target[..target.len() - 3]
84    } else {
85        target
86    };
87    let segments: Vec<String> = path.split("::").map(|s| s.to_string()).collect();
88    if segments.is_empty() {
89        return None;
90    }
91    Some(ImportEdge {
92        target_namespace: segments[0].clone(),
93        wildcard,
94        segments,
95    })
96}
97
98fn resolve_file_imports(
99    file: &str,
100    import_graph: &HashMap<String, Vec<ImportEdge>>,
101    namespace_map: &HashMap<String, String>,
102    exports: &HashMap<String, Vec<(String, String)>>,
103    visible: &mut HashMap<String, String>,
104    visited: &mut HashSet<String>,
105    elements: &[SysmlElement],
106) {
107    if visited.contains(file) {
108        return;
109    }
110    visited.insert(file.to_string());
111
112    let edges = match import_graph.get(file) {
113        Some(e) => e,
114        None => return,
115    };
116
117    for edge in edges {
118        let target_file = match namespace_map.get(&edge.target_namespace) {
119            Some(f) => f.clone(),
120            None => continue,
121        };
122
123        if edge.segments.len() == 1 && edge.wildcard {
124            resolve_file_imports(
125                &target_file,
126                import_graph,
127                namespace_map,
128                exports,
129                &mut HashMap::new(),
130                visited,
131                elements,
132            );
133
134            if let Some(target_exports) = exports.get(&target_file) {
135                for (name, qname) in target_exports {
136                    visible.insert(name.clone(), qname.clone());
137                }
138            }
139
140            let mut transitive = HashMap::new();
141            let mut tv = visited.clone();
142            resolve_file_imports(
143                &target_file,
144                import_graph,
145                namespace_map,
146                exports,
147                &mut transitive,
148                &mut tv,
149                elements,
150            );
151            for (name, qname) in transitive {
152                visible.entry(name).or_insert(qname);
153            }
154        } else if edge.segments.len() == 1 {
155            continue;
156        } else {
157            let remainder = &edge.segments[1..];
158            resolve_multi_segment(
159                &target_file,
160                remainder,
161                edge.wildcard,
162                exports,
163                elements,
164                visible,
165            );
166        }
167    }
168}
169
170fn resolve_multi_segment(
171    file: &str,
172    segments: &[String],
173    wildcard: bool,
174    exports: &HashMap<String, Vec<(String, String)>>,
175    elements: &[SysmlElement],
176    visible: &mut HashMap<String, String>,
177) {
178    let file_elements: Vec<&SysmlElement> = elements
179        .iter()
180        .filter(|e| e.file_path.to_string_lossy() == file)
181        .collect();
182
183    if segments.len() == 1 && !wildcard {
184        let name = &segments[0];
185        if let Some(file_exports) = exports.get(file) {
186            for (ename, qname) in file_exports {
187                if ename == name {
188                    visible.insert(name.clone(), qname.clone());
189                    return;
190                }
191            }
192        }
193    } else if segments.len() == 1 && wildcard {
194        let container_name = &segments[0];
195        for elem in &file_elements {
196            let short = elem
197                .qualified_name
198                .rsplit("::")
199                .next()
200                .unwrap_or(&elem.qualified_name);
201            if short == container_name {
202                for member_qname in &elem.members {
203                    let member_name = member_qname.rsplit("::").next().unwrap_or(member_qname);
204                    visible.insert(member_name.to_string(), member_qname.clone());
205                }
206                return;
207            }
208        }
209    } else if segments.len() >= 2 {
210        let container_name = &segments[0];
211        let sub_segments = &segments[1..];
212
213        for elem in &file_elements {
214            let short = elem
215                .qualified_name
216                .rsplit("::")
217                .next()
218                .unwrap_or(&elem.qualified_name);
219            if short == container_name && sub_segments.len() == 1 && !wildcard {
220                let target_name = &sub_segments[0];
221                for member_qname in &elem.members {
222                    let member_name = member_qname.rsplit("::").next().unwrap_or(member_qname);
223                    if member_name == target_name {
224                        visible.insert(target_name.clone(), member_qname.clone());
225                        return;
226                    }
227                }
228            }
229        }
230    }
231}
232
233fn qualify_reference(reference: &str, visible: &HashMap<String, String>) -> String {
234    if reference.contains("::") {
235        return reference.to_string();
236    }
237    visible
238        .get(reference)
239        .cloned()
240        .unwrap_or_else(|| reference.to_string())
241}
242
243pub fn resolve_imports(elements: &[SysmlElement], relationships: &mut [SysmlRelationship]) {
244    let namespace_map = build_namespace_map(elements);
245    let exports = build_export_table(elements);
246    let import_graph = build_import_graph(relationships);
247
248    let mut files: HashSet<String> = HashSet::new();
249    for elem in elements {
250        files.insert(elem.file_path.to_string_lossy().to_string());
251    }
252
253    let mut resolved: HashMap<String, HashMap<String, String>> = HashMap::new();
254    for file in &files {
255        let mut visible = HashMap::new();
256        let mut visited = HashSet::new();
257        resolve_file_imports(
258            file,
259            &import_graph,
260            &namespace_map,
261            &exports,
262            &mut visible,
263            &mut visited,
264            elements,
265        );
266        if !visible.is_empty() {
267            resolved.insert(file.clone(), visible);
268        }
269    }
270
271    for rel in relationships.iter_mut() {
272        let file = rel.file_path.to_string_lossy().to_string();
273        if let Some(visible) = resolved.get(&file) {
274            rel.source = qualify_reference(&rel.source, visible);
275            rel.target = qualify_reference(&rel.target, visible);
276        }
277    }
278}