Skip to main content

sysml_core/
walker.rs

1use std::path::PathBuf;
2
3use tree_sitter::Node;
4
5use nomograph_core::types::{Diagnostic, Severity, Span};
6
7use crate::element::SysmlElement;
8use crate::relationship::SysmlRelationship;
9
10pub fn is_element_node(kind: &str) -> bool {
11    kind.ends_with("_definition") || kind.ends_with("_usage") || kind == "library_package"
12}
13
14fn is_body_node(kind: &str) -> bool {
15    matches!(
16        kind,
17        "package_body"
18            | "structural_body"
19            | "usage_body"
20            | "action_body"
21            | "constraint_body"
22            | "enumeration_body"
23            | "requirement_body"
24            | "state_body"
25    )
26}
27
28fn strip_quotes(s: &str) -> &str {
29    s.strip_prefix('\'')
30        .and_then(|s| s.strip_suffix('\''))
31        .unwrap_or(s)
32}
33
34fn extract_element_name(node: &Node, source: &str) -> Option<String> {
35    for i in 0..node.child_count() {
36        if let Some(child) = node.child(i) {
37            if child.kind() == "identification" {
38                return find_name_in_identification(&child, source);
39            }
40        }
41    }
42
43    for i in 0..node.child_count() {
44        if let Some(child) = node.child(i) {
45            if child.kind() == "usage_declaration" {
46                for j in 0..child.child_count() {
47                    if let Some(gc) = child.child(j) {
48                        if gc.kind() == "identification" {
49                            return find_name_in_identification(&gc, source);
50                        }
51                    }
52                }
53                if let Some(rel_part) = find_child_by_kind(&child, "relationship_part") {
54                    if let Some(redef) = find_child_by_kind(&rel_part, "redefinition_part") {
55                        if let Some(fc) = find_child_by_kind(&redef, "feature_chain") {
56                            return extract_feature_chain_text(&fc, source);
57                        }
58                    }
59                }
60            }
61        }
62    }
63
64    if let Some(fc) = find_child_by_kind(node, "feature_chain") {
65        return extract_feature_chain_text(&fc, source);
66    }
67
68    None
69}
70
71fn find_name_in_identification(node: &Node, source: &str) -> Option<String> {
72    for i in 0..node.child_count() {
73        if let Some(child) = node.child(i) {
74            if child.kind() == "name" {
75                return child
76                    .utf8_text(source.as_bytes())
77                    .ok()
78                    .map(|s| strip_quotes(s).to_string());
79            }
80        }
81    }
82    None
83}
84
85fn extract_typed_by(node: &Node, source: &str) -> Option<String> {
86    let decl = find_child_by_kind(node, "usage_declaration")?;
87    let typing = find_child_by_kind(&decl, "typing_part")?;
88    let qname = find_child_by_kind(&typing, "qualified_name")?;
89    extract_qualified_name_text(&qname, source)
90}
91
92fn extract_qualified_name_text(node: &Node, source: &str) -> Option<String> {
93    let mut parts = Vec::new();
94    for i in 0..node.child_count() {
95        if let Some(child) = node.child(i) {
96            if child.kind() == "name" {
97                if let Ok(text) = child.utf8_text(source.as_bytes()) {
98                    parts.push(strip_quotes(text).to_string());
99                }
100            }
101        }
102    }
103    if parts.is_empty() {
104        None
105    } else {
106        Some(parts.join("::"))
107    }
108}
109
110fn find_child_by_kind<'a>(node: &Node<'a>, kind: &str) -> Option<Node<'a>> {
111    for i in 0..node.child_count() {
112        if let Some(child) = node.child(i) {
113            if child.kind() == kind {
114                return Some(child);
115            }
116        }
117    }
118    None
119}
120
121fn extract_doc_comment(body_node: &Node, source: &str) -> Option<String> {
122    for i in 0..body_node.child_count() {
123        if let Some(child) = body_node.child(i) {
124            if child.kind() == "documentation" {
125                if let Some(comment_body) = find_child_by_kind(&child, "block_comment_body") {
126                    if let Ok(text) = comment_body.utf8_text(source.as_bytes()) {
127                        return Some(clean_doc_comment(text));
128                    }
129                }
130            }
131        }
132    }
133    None
134}
135
136fn clean_doc_comment(text: &str) -> String {
137    let trimmed = text
138        .strip_prefix("/*")
139        .unwrap_or(text)
140        .strip_suffix("*/")
141        .unwrap_or(text);
142    trimmed
143        .lines()
144        .map(|line| {
145            let stripped = line.trim();
146            stripped
147                .strip_prefix("* ")
148                .unwrap_or(stripped.strip_prefix('*').unwrap_or(stripped))
149        })
150        .collect::<Vec<_>>()
151        .join(" ")
152        .trim()
153        .to_string()
154}
155
156fn find_body_child<'a>(node: &Node<'a>) -> Option<Node<'a>> {
157    for i in 0..node.child_count() {
158        if let Some(child) = node.child(i) {
159            if is_body_node(child.kind()) {
160                return Some(child);
161            }
162        }
163    }
164    None
165}
166
167fn extract_feature_chain_text(node: &Node, source: &str) -> Option<String> {
168    let mut parts = Vec::new();
169    for i in 0..node.child_count() {
170        if let Some(child) = node.child(i) {
171            if child.kind() == "name" {
172                if let Ok(text) = child.utf8_text(source.as_bytes()) {
173                    parts.push(strip_quotes(text).to_string());
174                }
175            }
176        }
177    }
178    if parts.is_empty() {
179        None
180    } else {
181        Some(parts.join("."))
182    }
183}
184
185fn find_children_by_kind<'a>(node: &Node<'a>, kind: &str) -> Vec<Node<'a>> {
186    let mut result = Vec::new();
187    for i in 0..node.child_count() {
188        if let Some(child) = node.child(i) {
189            if child.kind() == kind {
190                result.push(child);
191            }
192        }
193    }
194    result
195}
196
197fn extract_binding_value_text(node: &Node, source: &str) -> Option<String> {
198    for i in 0..node.child_count() {
199        if let Some(child) = node.child(i) {
200            if child.is_named() {
201                if let Some(fce) = find_child_by_kind(&child, "qualified_name") {
202                    return extract_qualified_name_text(&fce, source);
203                }
204                if let Some(fc) = find_child_by_kind(&child, "feature_chain") {
205                    return extract_feature_chain_text(&fc, source);
206                }
207                return child
208                    .utf8_text(source.as_bytes())
209                    .ok()
210                    .map(|s| s.trim().to_string());
211            }
212        }
213    }
214    None
215}
216
217fn extract_reference_text(node: &Node, source: &str) -> Option<String> {
218    if let Some(qname) = find_child_by_kind(node, "qualified_name") {
219        return extract_qualified_name_text(&qname, source);
220    }
221    if let Some(fc) = find_child_by_kind(node, "feature_chain") {
222        return extract_feature_chain_text(&fc, source);
223    }
224    if let Some(name) = find_child_by_kind(node, "name") {
225        return name
226            .utf8_text(source.as_bytes())
227            .ok()
228            .map(|s| strip_quotes(s).to_string());
229    }
230    None
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
234pub(crate) enum RelationshipKind {
235    Satisfy,
236    Verify,
237    Import,
238    Specialize,
239    Allocate,
240    Connect,
241    Bind,
242    Flow,
243    Stream,
244    Dependency,
245    Redefine,
246    Expose,
247    Perform,
248    Exhibit,
249    Include,
250    Succession,
251    Transition,
252    Send,
253    Accept,
254    Require,
255    Assume,
256    Assert,
257    Assign,
258    Subject,
259    Render,
260    Frame,
261    Message,
262    TypedBy,
263    Member,
264}
265
266impl RelationshipKind {
267    #[cfg(test)]
268    pub(crate) const ALL: &[RelationshipKind] = &[
269        Self::Satisfy,
270        Self::Verify,
271        Self::Import,
272        Self::Specialize,
273        Self::Allocate,
274        Self::Connect,
275        Self::Bind,
276        Self::Flow,
277        Self::Stream,
278        Self::Dependency,
279        Self::Redefine,
280        Self::Expose,
281        Self::Perform,
282        Self::Exhibit,
283        Self::Include,
284        Self::Succession,
285        Self::Transition,
286        Self::Send,
287        Self::Accept,
288        Self::Require,
289        Self::Assume,
290        Self::Assert,
291        Self::Assign,
292        Self::Subject,
293        Self::Render,
294        Self::Frame,
295        Self::Message,
296        Self::TypedBy,
297        Self::Member,
298    ];
299}
300
301impl std::fmt::Display for RelationshipKind {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        match self {
304            Self::Satisfy => write!(f, "Satisfy"),
305            Self::Verify => write!(f, "Verify"),
306            Self::Import => write!(f, "Import"),
307            Self::Specialize => write!(f, "Specialize"),
308            Self::Allocate => write!(f, "Allocate"),
309            Self::Connect => write!(f, "Connect"),
310            Self::Bind => write!(f, "Bind"),
311            Self::Flow => write!(f, "Flow"),
312            Self::Stream => write!(f, "Stream"),
313            Self::Dependency => write!(f, "Dependency"),
314            Self::Redefine => write!(f, "Redefine"),
315            Self::Expose => write!(f, "Expose"),
316            Self::Perform => write!(f, "Perform"),
317            Self::Exhibit => write!(f, "Exhibit"),
318            Self::Include => write!(f, "Include"),
319            Self::Succession => write!(f, "Succession"),
320            Self::Transition => write!(f, "Transition"),
321            Self::Send => write!(f, "Send"),
322            Self::Accept => write!(f, "Accept"),
323            Self::Require => write!(f, "Require"),
324            Self::Assume => write!(f, "Assume"),
325            Self::Assert => write!(f, "Assert"),
326            Self::Assign => write!(f, "Assign"),
327            Self::Subject => write!(f, "Subject"),
328            Self::Render => write!(f, "Render"),
329            Self::Frame => write!(f, "Frame"),
330            Self::Message => write!(f, "Message"),
331            Self::TypedBy => write!(f, "TypedBy"),
332            Self::Member => write!(f, "Member"),
333        }
334    }
335}
336
337pub(crate) const RELATIONSHIP_DISPATCH: &[(&str, RelationshipKind)] = &[
338    ("satisfy_statement", RelationshipKind::Satisfy),
339    ("verify_statement", RelationshipKind::Verify),
340    ("import_statement", RelationshipKind::Import),
341    ("definition_specialization", RelationshipKind::Specialize),
342    ("allocate_statement", RelationshipKind::Allocate),
343    ("connect_statement", RelationshipKind::Connect),
344    ("bind_statement", RelationshipKind::Bind),
345    ("flow_statement", RelationshipKind::Flow),
346    ("flow_usage", RelationshipKind::Flow),
347    ("stream_statement", RelationshipKind::Stream),
348    ("dependency", RelationshipKind::Dependency),
349    ("perform_statement", RelationshipKind::Perform),
350    ("exhibit_usage", RelationshipKind::Exhibit),
351    ("include_statement", RelationshipKind::Include),
352    ("expose_statement", RelationshipKind::Expose),
353    ("then_succession", RelationshipKind::Succession),
354    ("succession_statement", RelationshipKind::Succession),
355    ("first_statement", RelationshipKind::Succession),
356    ("message_statement", RelationshipKind::Message),
357    ("redefines_statement", RelationshipKind::Redefine),
358    ("specialization_statement", RelationshipKind::Specialize),
359    ("transition_statement", RelationshipKind::Transition),
360    ("send_statement", RelationshipKind::Send),
361    ("accept_then_statement", RelationshipKind::Accept),
362    ("require_statement", RelationshipKind::Require),
363    ("assume_statement", RelationshipKind::Assume),
364    ("assert_statement", RelationshipKind::Assert),
365    ("assign_statement", RelationshipKind::Assign),
366    ("subject_statement", RelationshipKind::Subject),
367    ("render_statement", RelationshipKind::Render),
368    ("frame_statement", RelationshipKind::Frame),
369];
370
371fn dispatch_relationship_kind(node_kind: &str) -> Option<RelationshipKind> {
372    RELATIONSHIP_DISPATCH
373        .iter()
374        .find(|(k, _)| *k == node_kind)
375        .map(|(_, v)| *v)
376}
377
378fn node_span(node: &Node) -> Span {
379    let start = node.start_position();
380    let end = node.end_position();
381    Span {
382        start_line: start.row as u32,
383        start_col: start.column as u32,
384        end_line: end.row as u32,
385        end_col: end.column as u32,
386    }
387}
388
389pub struct Walker<'a> {
390    source: &'a str,
391    file_path: PathBuf,
392    name_stack: Vec<String>,
393    pub elements: Vec<SysmlElement>,
394    pub relationships: Vec<SysmlRelationship>,
395    anon_counter: usize,
396}
397
398impl<'a> Walker<'a> {
399    pub fn new(source: &'a str, file_path: PathBuf) -> Self {
400        Self {
401            source,
402            file_path,
403            name_stack: Vec::new(),
404            elements: Vec::new(),
405            relationships: Vec::new(),
406            anon_counter: 0,
407        }
408    }
409
410    fn qualified_name(&self) -> String {
411        self.name_stack.join("::")
412    }
413
414    pub fn walk_root(&mut self, root: Node<'a>) {
415        for i in 0..root.child_count() {
416            if let Some(child) = root.child(i) {
417                if child.is_named() {
418                    self.walk_node(child);
419                }
420            }
421        }
422    }
423
424    fn walk_node(&mut self, node: Node<'a>) {
425        let kind = node.kind();
426        if !is_element_node(kind) {
427            return;
428        }
429
430        let name = extract_element_name(&node, self.source);
431        if name.is_none() {
432            return;
433        }
434
435        let display_name = name.clone().unwrap_or_else(|| {
436            self.anon_counter += 1;
437            format!("<anonymous_{}>", self.anon_counter)
438        });
439
440        self.name_stack.push(display_name);
441        let qname = self.qualified_name();
442        let span = node_span(&node);
443
444        let body = find_body_child(&node);
445        let doc = body
446            .as_ref()
447            .and_then(|b| extract_doc_comment(b, self.source));
448        let typed_by = extract_typed_by(&node, self.source);
449        let members = body
450            .as_ref()
451            .map(|b| self.collect_member_names(b, &qname))
452            .unwrap_or_default();
453
454        if let Some(ref ty) = typed_by {
455            self.relationships.push(SysmlRelationship {
456                source: qname.clone(),
457                target: ty.clone(),
458                kind: RelationshipKind::TypedBy.to_string(),
459                file_path: self.file_path.clone(),
460                span: span.clone(),
461            });
462        }
463
464        let parent_qname = if self.name_stack.len() > 1 {
465            Some(self.name_stack[..self.name_stack.len() - 1].join("::"))
466        } else {
467            None
468        };
469
470        if let Some(ref pq) = parent_qname {
471            self.relationships.push(SysmlRelationship {
472                source: pq.clone(),
473                target: qname.clone(),
474                kind: RelationshipKind::Member.to_string(),
475                file_path: self.file_path.clone(),
476                span: span.clone(),
477            });
478        }
479
480        self.extract_definition_relationships(&node, &qname);
481        self.extract_binding_value(&node, &qname);
482
483        let elem = SysmlElement {
484            qualified_name: qname.clone(),
485            kind: kind.to_string(),
486            file_path: self.file_path.clone(),
487            span,
488            doc,
489            attributes: Vec::new(),
490            members,
491            layer: crate::vocabulary::classify_layer(kind),
492        };
493        self.elements.push(elem);
494
495        if let Some(body) = find_body_child(&node) {
496            self.extract_body_relationships(&body, &qname);
497            for i in 0..body.child_count() {
498                if let Some(child) = body.child(i) {
499                    if child.is_named() {
500                        self.walk_node(child);
501                    }
502                }
503            }
504        }
505
506        self.name_stack.pop();
507    }
508
509    fn collect_member_names(&self, body: &Node, parent_qname: &str) -> Vec<String> {
510        let mut names = Vec::new();
511        for i in 0..body.child_count() {
512            if let Some(child) = body.child(i) {
513                if is_element_node(child.kind()) {
514                    if let Some(name) = extract_element_name(&child, self.source) {
515                        names.push(format!("{}::{}", parent_qname, name));
516                    }
517                }
518            }
519        }
520        names
521    }
522
523    fn extract_definition_relationships(&mut self, node: &Node, qname: &str) {
524        if let Some(spec) = find_child_by_kind(node, "definition_specialization") {
525            let span = node_span(&spec);
526            for qn in find_children_by_kind(&spec, "qualified_name") {
527                if let Some(target) = extract_qualified_name_text(&qn, self.source) {
528                    self.relationships.push(SysmlRelationship {
529                        source: qname.to_string(),
530                        target,
531                        kind: RelationshipKind::Specialize.to_string(),
532                        file_path: self.file_path.clone(),
533                        span: span.clone(),
534                    });
535                }
536            }
537        }
538
539        if let Some(decl) = find_child_by_kind(node, "usage_declaration") {
540            if let Some(rel_part) = find_child_by_kind(&decl, "relationship_part") {
541                if let Some(redef) = find_child_by_kind(&rel_part, "redefinition_part") {
542                    let span = node_span(&redef);
543                    if let Some(fc) = find_child_by_kind(&redef, "feature_chain") {
544                        if let Some(target) = extract_feature_chain_text(&fc, self.source) {
545                            self.relationships.push(SysmlRelationship {
546                                source: qname.to_string(),
547                                target,
548                                kind: RelationshipKind::Redefine.to_string(),
549                                file_path: self.file_path.clone(),
550                                span,
551                            });
552                        }
553                    }
554                }
555                if let Some(spec) = find_child_by_kind(&rel_part, "specialization_part") {
556                    let span = node_span(&spec);
557                    for fc in find_children_by_kind(&spec, "feature_chain") {
558                        if let Some(target) = extract_feature_chain_text(&fc, self.source) {
559                            self.relationships.push(SysmlRelationship {
560                                source: qname.to_string(),
561                                target,
562                                kind: RelationshipKind::Specialize.to_string(),
563                                file_path: self.file_path.clone(),
564                                span: span.clone(),
565                            });
566                        }
567                    }
568                    for qn in find_children_by_kind(&spec, "qualified_name") {
569                        if let Some(target) = extract_qualified_name_text(&qn, self.source) {
570                            self.relationships.push(SysmlRelationship {
571                                source: qname.to_string(),
572                                target,
573                                kind: RelationshipKind::Specialize.to_string(),
574                                file_path: self.file_path.clone(),
575                                span: span.clone(),
576                            });
577                        }
578                    }
579                }
580            }
581        }
582    }
583
584    fn extract_binding_value(&mut self, node: &Node, qname: &str) {
585        let value = find_child_by_kind(node, "value_part").or_else(|| {
586            find_child_by_kind(node, "usage_declaration").and_then(|decl| {
587                find_child_by_kind(&decl, "relationship_part").and_then(|rp| {
588                    find_child_by_kind(&rp, "redefinition_part")
589                        .and_then(|rd| find_child_by_kind(&rd, "value_part"))
590                })
591            })
592        });
593        if let Some(vp) = value {
594            let target = extract_reference_text(&vp, self.source)
595                .or_else(|| extract_binding_value_text(&vp, self.source));
596            if let Some(target) = target {
597                self.relationships.push(SysmlRelationship {
598                    source: qname.to_string(),
599                    target,
600                    kind: RelationshipKind::Bind.to_string(),
601                    file_path: self.file_path.clone(),
602                    span: node_span(&vp),
603                });
604            }
605        }
606    }
607
608    fn extract_body_relationships(&mut self, body: &Node, context_qname: &str) {
609        for i in 0..body.child_count() {
610            if let Some(child) = body.child(i) {
611                if !child.is_named() {
612                    continue;
613                }
614                let kind = child.kind();
615                if let Some(rel_kind) = dispatch_relationship_kind(kind) {
616                    self.extract_relationship(&child, rel_kind, context_qname);
617                }
618                if kind == "allocate_statement" {
619                    self.extract_nested_allocates(&child, context_qname);
620                }
621            }
622        }
623    }
624
625    fn extract_relationship(
626        &mut self,
627        node: &Node,
628        rel_kind: RelationshipKind,
629        context_qname: &str,
630    ) {
631        let span = node_span(node);
632        let file_path = self.file_path.clone();
633
634        match rel_kind {
635            RelationshipKind::Satisfy => {
636                let qnames = find_children_by_kind(node, "qualified_name");
637                let fchains = find_children_by_kind(node, "feature_chain");
638                let target = qnames
639                    .first()
640                    .and_then(|n| extract_qualified_name_text(n, self.source))
641                    .unwrap_or_default();
642                let source = fchains
643                    .first()
644                    .and_then(|n| extract_feature_chain_text(n, self.source))
645                    .unwrap_or_else(|| context_qname.to_string());
646                if !target.is_empty() {
647                    self.relationships.push(SysmlRelationship {
648                        source,
649                        target,
650                        kind: rel_kind.to_string(),
651                        file_path,
652                        span,
653                    });
654                }
655            }
656            RelationshipKind::Verify => {
657                if let Some(target) = extract_reference_text(node, self.source) {
658                    self.relationships.push(SysmlRelationship {
659                        source: context_qname.to_string(),
660                        target,
661                        kind: rel_kind.to_string(),
662                        file_path,
663                        span,
664                    });
665                }
666            }
667            RelationshipKind::Import => {
668                if let Some(import_ref) = find_child_by_kind(node, "import_reference") {
669                    let target = if let Some(wildcard) =
670                        find_child_by_kind(&import_ref, "wildcard_import")
671                    {
672                        find_child_by_kind(&wildcard, "name").and_then(|n| {
673                            n.utf8_text(self.source.as_bytes())
674                                .ok()
675                                .map(|s| format!("{}::*", strip_quotes(s)))
676                        })
677                    } else if let Some(qn) = find_child_by_kind(&import_ref, "qualified_name") {
678                        extract_qualified_name_text(&qn, self.source)
679                    } else {
680                        None
681                    };
682                    if let Some(target) = target {
683                        self.relationships.push(SysmlRelationship {
684                            source: context_qname.to_string(),
685                            target,
686                            kind: rel_kind.to_string(),
687                            file_path,
688                            span,
689                        });
690                    }
691                }
692            }
693            RelationshipKind::Connect => {
694                let endpoints = find_children_by_kind(node, "connect_endpoint");
695                let texts: Vec<String> = endpoints
696                    .iter()
697                    .filter_map(|ep| {
698                        find_child_by_kind(ep, "feature_chain")
699                            .and_then(|fc| extract_feature_chain_text(&fc, self.source))
700                    })
701                    .collect();
702                if texts.len() >= 2 {
703                    self.relationships.push(SysmlRelationship {
704                        source: texts[0].clone(),
705                        target: texts[1].clone(),
706                        kind: rel_kind.to_string(),
707                        file_path,
708                        span,
709                    });
710                }
711            }
712            RelationshipKind::Allocate => {
713                let fchains = find_children_by_kind(node, "feature_chain");
714                if fchains.len() >= 2 {
715                    let source =
716                        extract_feature_chain_text(&fchains[0], self.source).unwrap_or_default();
717                    let target =
718                        extract_feature_chain_text(&fchains[1], self.source).unwrap_or_default();
719                    if !source.is_empty() && !target.is_empty() {
720                        self.relationships.push(SysmlRelationship {
721                            source,
722                            target,
723                            kind: rel_kind.to_string(),
724                            file_path,
725                            span,
726                        });
727                    }
728                }
729            }
730            RelationshipKind::Flow => {
731                let fchains = if node.kind() == "flow_usage" {
732                    find_child_by_kind(node, "flow_part")
733                        .map(|fp| find_children_by_kind(&fp, "feature_chain"))
734                        .unwrap_or_default()
735                } else {
736                    find_children_by_kind(node, "feature_chain")
737                };
738                if fchains.len() >= 2 {
739                    let source =
740                        extract_feature_chain_text(&fchains[0], self.source).unwrap_or_default();
741                    let target =
742                        extract_feature_chain_text(&fchains[1], self.source).unwrap_or_default();
743                    if !source.is_empty() && !target.is_empty() {
744                        self.relationships.push(SysmlRelationship {
745                            source,
746                            target,
747                            kind: rel_kind.to_string(),
748                            file_path,
749                            span,
750                        });
751                    }
752                }
753            }
754            RelationshipKind::Dependency => {
755                let qnames = find_children_by_kind(node, "qualified_name");
756                if qnames.len() >= 2 {
757                    let source =
758                        extract_qualified_name_text(&qnames[0], self.source).unwrap_or_default();
759                    for qn in &qnames[1..] {
760                        if let Some(target) = extract_qualified_name_text(qn, self.source) {
761                            self.relationships.push(SysmlRelationship {
762                                source: source.clone(),
763                                target,
764                                kind: rel_kind.to_string(),
765                                file_path: file_path.clone(),
766                                span: span.clone(),
767                            });
768                        }
769                    }
770                }
771            }
772            RelationshipKind::Perform => {
773                if let Some(target) = find_child_by_kind(node, "feature_chain")
774                    .and_then(|fc| extract_feature_chain_text(&fc, self.source))
775                {
776                    self.relationships.push(SysmlRelationship {
777                        source: context_qname.to_string(),
778                        target,
779                        kind: rel_kind.to_string(),
780                        file_path,
781                        span,
782                    });
783                }
784            }
785            RelationshipKind::Exhibit => {
786                let target = find_child_by_kind(node, "feature_chain")
787                    .and_then(|fc| extract_feature_chain_text(&fc, self.source))
788                    .or_else(|| {
789                        find_child_by_kind(node, "qualified_name")
790                            .and_then(|qn| extract_qualified_name_text(&qn, self.source))
791                    });
792                if let Some(target) = target {
793                    self.relationships.push(SysmlRelationship {
794                        source: context_qname.to_string(),
795                        target,
796                        kind: rel_kind.to_string(),
797                        file_path,
798                        span,
799                    });
800                }
801            }
802            RelationshipKind::Succession => {
803                let node_kind = node.kind();
804                if node_kind == "succession_statement" {
805                    let fchains = find_children_by_kind(node, "feature_chain");
806                    if fchains.len() >= 2 {
807                        let source = extract_feature_chain_text(&fchains[0], self.source)
808                            .unwrap_or_default();
809                        let target = extract_feature_chain_text(&fchains[1], self.source)
810                            .unwrap_or_default();
811                        if !source.is_empty() && !target.is_empty() {
812                            self.relationships.push(SysmlRelationship {
813                                source,
814                                target,
815                                kind: rel_kind.to_string(),
816                                file_path,
817                                span,
818                            });
819                        }
820                    }
821                } else if node_kind == "first_statement" {
822                    let fchains = find_children_by_kind(node, "feature_chain");
823                    if fchains.len() >= 2 {
824                        let source = extract_feature_chain_text(&fchains[0], self.source)
825                            .unwrap_or_default();
826                        let target = extract_feature_chain_text(&fchains[1], self.source)
827                            .unwrap_or_default();
828                        if !source.is_empty() && !target.is_empty() {
829                            self.relationships.push(SysmlRelationship {
830                                source,
831                                target,
832                                kind: rel_kind.to_string(),
833                                file_path,
834                                span,
835                            });
836                        }
837                    } else if let Some(target) = find_child_by_kind(node, "name").and_then(|n| {
838                        n.utf8_text(self.source.as_bytes())
839                            .ok()
840                            .map(|s| strip_quotes(s).to_string())
841                    }) {
842                        self.relationships.push(SysmlRelationship {
843                            source: context_qname.to_string(),
844                            target,
845                            kind: rel_kind.to_string(),
846                            file_path,
847                            span,
848                        });
849                    }
850                } else if let Some(target) = find_child_by_kind(node, "name").and_then(|n| {
851                    n.utf8_text(self.source.as_bytes())
852                        .ok()
853                        .map(|s| strip_quotes(s).to_string())
854                }) {
855                    self.relationships.push(SysmlRelationship {
856                        source: context_qname.to_string(),
857                        target,
858                        kind: rel_kind.to_string(),
859                        file_path,
860                        span,
861                    });
862                }
863            }
864            RelationshipKind::Message => {
865                let fchains = find_children_by_kind(node, "feature_chain");
866                if fchains.len() >= 2 {
867                    let source =
868                        extract_feature_chain_text(&fchains[0], self.source).unwrap_or_default();
869                    let target =
870                        extract_feature_chain_text(&fchains[1], self.source).unwrap_or_default();
871                    if !source.is_empty() && !target.is_empty() {
872                        self.relationships.push(SysmlRelationship {
873                            source,
874                            target,
875                            kind: rel_kind.to_string(),
876                            file_path,
877                            span,
878                        });
879                    }
880                } else if let Some(target) = extract_reference_text(node, self.source) {
881                    self.relationships.push(SysmlRelationship {
882                        source: context_qname.to_string(),
883                        target,
884                        kind: rel_kind.to_string(),
885                        file_path,
886                        span,
887                    });
888                }
889            }
890            RelationshipKind::Stream => {
891                let fchains = find_children_by_kind(node, "feature_chain");
892                if fchains.len() >= 2 {
893                    let source =
894                        extract_feature_chain_text(&fchains[0], self.source).unwrap_or_default();
895                    let target =
896                        extract_feature_chain_text(&fchains[1], self.source).unwrap_or_default();
897                    if !source.is_empty() && !target.is_empty() {
898                        self.relationships.push(SysmlRelationship {
899                            source,
900                            target,
901                            kind: rel_kind.to_string(),
902                            file_path,
903                            span,
904                        });
905                    }
906                }
907            }
908            RelationshipKind::Accept => {
909                if let Some(trigger) = find_child_by_kind(node, "trigger_kind") {
910                    if let Some(target) = extract_reference_text(&trigger, self.source) {
911                        self.relationships.push(SysmlRelationship {
912                            source: context_qname.to_string(),
913                            target,
914                            kind: rel_kind.to_string(),
915                            file_path,
916                            span,
917                        });
918                    }
919                }
920            }
921            RelationshipKind::Assert => {
922                let mut found_nested = false;
923                for i in 0..node.child_count() {
924                    if let Some(child) = node.child(i) {
925                        let ck = child.kind();
926                        if ck == "satisfy_statement" {
927                            self.extract_relationship(
928                                &child,
929                                RelationshipKind::Satisfy,
930                                context_qname,
931                            );
932                            found_nested = true;
933                        } else if ck == "verify_statement" {
934                            self.extract_relationship(
935                                &child,
936                                RelationshipKind::Verify,
937                                context_qname,
938                            );
939                            found_nested = true;
940                        }
941                    }
942                }
943                if !found_nested {
944                    if let Some(target) = extract_reference_text(node, self.source) {
945                        self.relationships.push(SysmlRelationship {
946                            source: context_qname.to_string(),
947                            target,
948                            kind: rel_kind.to_string(),
949                            file_path,
950                            span,
951                        });
952                    }
953                }
954            }
955            RelationshipKind::Redefine if node.kind() == "redefines_statement" => {
956                let qnames = find_children_by_kind(node, "qualified_name");
957                let fchains = find_children_by_kind(node, "feature_chain");
958                for qn in &qnames {
959                    if let Some(target) = extract_qualified_name_text(qn, self.source) {
960                        self.relationships.push(SysmlRelationship {
961                            source: context_qname.to_string(),
962                            target,
963                            kind: rel_kind.to_string(),
964                            file_path: file_path.clone(),
965                            span: span.clone(),
966                        });
967                    }
968                }
969                for fc in &fchains {
970                    if let Some(target) = extract_feature_chain_text(fc, self.source) {
971                        self.relationships.push(SysmlRelationship {
972                            source: context_qname.to_string(),
973                            target,
974                            kind: rel_kind.to_string(),
975                            file_path: file_path.clone(),
976                            span: span.clone(),
977                        });
978                    }
979                }
980            }
981            RelationshipKind::Specialize if node.kind() == "specialization_statement" => {
982                if let Some(target) = find_child_by_kind(node, "qualified_name")
983                    .and_then(|qn| extract_qualified_name_text(&qn, self.source))
984                {
985                    self.relationships.push(SysmlRelationship {
986                        source: context_qname.to_string(),
987                        target,
988                        kind: rel_kind.to_string(),
989                        file_path,
990                        span,
991                    });
992                }
993            }
994            _ => {
995                if let Some(target) = extract_reference_text(node, self.source) {
996                    self.relationships.push(SysmlRelationship {
997                        source: context_qname.to_string(),
998                        target,
999                        kind: rel_kind.to_string(),
1000                        file_path,
1001                        span,
1002                    });
1003                }
1004            }
1005        }
1006    }
1007
1008    fn extract_nested_allocates(&mut self, node: &Node, context_qname: &str) {
1009        for i in 0..node.child_count() {
1010            if let Some(child) = node.child(i) {
1011                if child.kind() == "allocate_statement" {
1012                    self.extract_relationship(&child, RelationshipKind::Allocate, context_qname);
1013                    self.extract_nested_allocates(&child, context_qname);
1014                }
1015            }
1016        }
1017    }
1018}
1019
1020pub fn collect_parse_errors(node: Node, source: &str, diagnostics: &mut Vec<Diagnostic>) {
1021    if node.is_error() {
1022        let start = node.start_position();
1023        let end = node.end_position();
1024        let text = node.utf8_text(source.as_bytes()).unwrap_or("<invalid>");
1025        let context = if text.len() > 30 {
1026            format!("{}...", &text[..30])
1027        } else {
1028            text.to_string()
1029        };
1030        diagnostics.push(Diagnostic {
1031            severity: Severity::Error,
1032            message: format!("Syntax error near '{}'", context),
1033            span: Span {
1034                start_line: start.row as u32,
1035                start_col: start.column as u32,
1036                end_line: end.row as u32,
1037                end_col: end.column as u32,
1038            },
1039        });
1040    } else if node.is_missing() {
1041        let start = node.start_position();
1042        let end = node.end_position();
1043        diagnostics.push(Diagnostic {
1044            severity: Severity::Error,
1045            message: format!("Missing {}", node.kind()),
1046            span: Span {
1047                start_line: start.row as u32,
1048                start_col: start.column as u32,
1049                end_line: end.row as u32,
1050                end_col: end.column as u32,
1051            },
1052        });
1053    }
1054
1055    for i in 0..node.child_count() {
1056        if let Some(child) = node.child(i) {
1057            collect_parse_errors(child, source, diagnostics);
1058        }
1059    }
1060}