relay-knowledge 1.1.6

Graph-database-based knowledge graph project.
Documentation
use tree_sitter::Node;

use super::super::super::recovery::{
    c_family_typedef_like_function_signature, decorated_function_error_body_is_statement_like,
    decorated_function_head_has_recoverable_tail, decorated_function_head_has_recovery_decorator,
    decorated_function_head_text, scan::scan_code_line_indices,
};
use super::{
    SyntaxRange, c_declaration_prefix_token, c_identifier_char, data_symbol_name, node_text,
    syntax_range,
};

pub(super) fn gcc_decorated_function_symbol(
    content: &str,
    node: Node<'_>,
) -> Option<(String, &'static str, SyntaxRange)> {
    let text = node_text(content, node);
    if !text.contains('{') {
        return None;
    }
    let head = decorated_function_head_text(&text)?;
    if !decorated_function_head_has_recovery_decorator(head)
        || !decorated_function_head_has_recoverable_tail(head, false, false, false)
        || !decorated_function_error_body_is_statement_like(&text)
    {
        return None;
    }
    let name = c_function_name_from_decorated_head(head)?;
    (c_family_typedef_like_function_signature(head)
        && c_decorated_function_head_is_declaration(head, &name))
    .then(|| (name, "function", syntax_range(node)))
}

fn c_function_name_from_decorated_head(head: &str) -> Option<String> {
    let (name_start, name_end) = c_function_name_bounds_from_decorated_head(head)?;
    let name = &head[name_start..name_end];

    data_symbol_name(name).then(|| name.to_owned())
}

fn c_function_name_bounds_from_decorated_head(head: &str) -> Option<(usize, usize)> {
    let parameter_start = c_top_level_parameter_start(head)?;
    let name_end = head[..parameter_start].trim_end().len();
    let name_start = head[..name_end]
        .char_indices()
        .rev()
        .find(|(_, character)| !c_identifier_char(*character))
        .map_or(0, |(index, character)| index + character.len_utf8());
    (name_start < name_end).then_some((name_start, name_end))
}

fn c_top_level_parameter_start(head: &str) -> Option<usize> {
    let mut depth = 0usize;
    let mut candidate = None;
    let literals_closed = scan_code_line_indices(head, |index, character| match character {
        '(' => {
            if depth == 0 && c_parameter_open_looks_like_declarator(head, index) {
                candidate = Some(index);
            }
            depth += 1;
        }
        ')' => depth = depth.saturating_sub(1),
        _ => {}
    });

    literals_closed.then_some(candidate).flatten()
}

fn c_parameter_open_looks_like_declarator(head: &str, parameter_start: usize) -> bool {
    let Some((name_start, name_end)) = c_name_bounds_before_open(head, parameter_start) else {
        return false;
    };
    let token = &head[name_start..name_end];
    data_symbol_name(token) && !c_declaration_prefix_token(token)
}

fn c_name_bounds_before_open(head: &str, parameter_start: usize) -> Option<(usize, usize)> {
    let name_end = head[..parameter_start].trim_end().len();
    let name_start = head[..name_end]
        .char_indices()
        .rev()
        .find(|(_, character)| !c_identifier_char(*character))
        .map_or(0, |(index, character)| index + character.len_utf8());
    (name_start < name_end).then_some((name_start, name_end))
}

fn c_decorated_function_head_is_declaration(head: &str, name: &str) -> bool {
    if name.is_empty() {
        return false;
    }
    let Some((name_start, name_end)) = c_function_name_bounds_from_decorated_head(head) else {
        return false;
    };
    &head[name_start..name_end] == name
}