Skip to main content

brokk_bifrost_cpp/graph/
syntax.rs

1use tree_sitter::Node;
2
3#[derive(Clone)]
4pub struct QualifiedCallableValue<'tree> {
5    pub qualified: Node<'tree>,
6    pub global: bool,
7    pub owner_components: Vec<Node<'tree>>,
8    pub member: Node<'tree>,
9}
10
11/// Recognize an explicit address-of qualified callable value such as
12/// `&Owner::method` or `&namespace::Owner::method`.
13///
14/// The returned nodes come exclusively from the C++ grammar's named fields. In
15/// particular, a nested namespace/type owner remains a structured subtree rather
16/// than being reconstructed from source text.
17pub fn explicit_qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
18    if node.kind() != "pointer_expression" || node.child_by_field_name("operator")?.kind() != "&" {
19        return None;
20    }
21    let qualified = node.child_by_field_name("argument")?;
22    qualified_callable_value_from_node(qualified)
23}
24
25/// Recognize a qualified callable used as an expression value.
26///
27/// Calls use their own arity-aware path. Address-of expressions use the
28/// explicit path above. This arm covers structured values such as
29/// `bind(Owner::method)` and `callback = namespace::function`.
30pub fn qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
31    if let Some(value) = explicit_qualified_callable_value(node) {
32        return Some(value);
33    }
34    if node.kind() != "qualified_identifier" {
35        return None;
36    }
37    if crate::graph::resolver::is_declaration_name(node) {
38        return None;
39    }
40    if node.parent().is_some_and(|parent| {
41        parent.child_by_field_name("type") == Some(node)
42            || (parent.kind() == "call_expression"
43                && parent.child_by_field_name("function") == Some(node))
44            || (parent.kind() == "pointer_expression"
45                && parent.child_by_field_name("argument") == Some(node))
46            || matches!(
47                parent.kind(),
48                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
49            )
50    }) {
51        return None;
52    }
53    qualified_callable_value_from_node(node)
54}
55
56fn qualified_callable_value_from_node(qualified: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
57    if qualified.kind() != "qualified_identifier" {
58        return None;
59    }
60    let mut components = Vec::new();
61    let global = qualified.child_by_field_name("scope").is_none()
62        && qualified.child(0).is_some_and(|child| child.kind() == "::");
63    append_qualified_components(qualified, &mut components)?;
64    let member = components.pop()?;
65    if components.is_empty() {
66        return None;
67    }
68    Some(QualifiedCallableValue {
69        qualified,
70        global,
71        owner_components: components,
72        member,
73    })
74}
75
76fn append_qualified_components<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) -> Option<()> {
77    let mut stack = vec![node];
78    while let Some(current) = stack.pop() {
79        match current.kind() {
80            "identifier" | "namespace_identifier" | "type_identifier" | "operator_name" => {
81                out.push(current)
82            }
83            "qualified_identifier" | "scoped_identifier" => {
84                stack.push(current.child_by_field_name("name")?);
85                if let Some(scope) = current.child_by_field_name("scope") {
86                    stack.push(scope);
87                } else if current.child(0).is_none_or(|child| child.kind() != "::") {
88                    return None;
89                }
90            }
91            "template_type" | "template_function" => {
92                stack.push(current.child_by_field_name("name")?);
93            }
94            "nested_namespace_specifier" => {
95                for index in (0..current.named_child_count()).rev() {
96                    stack.push(current.named_child(index)?);
97                }
98            }
99            _ => return None,
100        }
101    }
102    Some(())
103}