Skip to main content

brokk_bifrost_cpp/graph/
syntax.rs

1use tree_sitter::Node;
2
3#[derive(Clone)]
4pub struct ExplicitQualifiedCallableValue<'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(
18    node: Node<'_>,
19) -> Option<ExplicitQualifiedCallableValue<'_>> {
20    if node.kind() != "pointer_expression" || node.child_by_field_name("operator")?.kind() != "&" {
21        return None;
22    }
23    let qualified = node.child_by_field_name("argument")?;
24    if qualified.kind() != "qualified_identifier" {
25        return None;
26    }
27    let mut components = Vec::new();
28    let global = qualified.child_by_field_name("scope").is_none()
29        && qualified.child(0).is_some_and(|child| child.kind() == "::");
30    append_qualified_components(qualified, &mut components)?;
31    let member = components.pop()?;
32    if components.is_empty() {
33        return None;
34    }
35    Some(ExplicitQualifiedCallableValue {
36        qualified,
37        global,
38        owner_components: components,
39        member,
40    })
41}
42
43fn append_qualified_components<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) -> Option<()> {
44    let mut stack = vec![node];
45    while let Some(current) = stack.pop() {
46        match current.kind() {
47            "identifier" | "namespace_identifier" | "type_identifier" => out.push(current),
48            "qualified_identifier" | "scoped_identifier" => {
49                stack.push(current.child_by_field_name("name")?);
50                if let Some(scope) = current.child_by_field_name("scope") {
51                    stack.push(scope);
52                } else if current.child(0).is_none_or(|child| child.kind() != "::") {
53                    return None;
54                }
55            }
56            "template_type" | "template_function" => {
57                stack.push(current.child_by_field_name("name")?);
58            }
59            "nested_namespace_specifier" => {
60                for index in (0..current.named_child_count()).rev() {
61                    stack.push(current.named_child(index)?);
62                }
63            }
64            _ => return None,
65        }
66    }
67    Some(())
68}