use tree_sitter::Node;
use super::intelligence::{node_text, span_between_from};
use super::types::*;
use super::walk::{Descend, walk_bounded, warn_if_truncated};
pub(super) fn structure(root: &Node<'_>, source: &str) -> Vec<StructureItem> {
let mut items = Vec::new();
let truncated = walk_bounded(root, |node, _depth| {
match node.kind() {
"translation_unit"
| "preproc_ifdef"
| "preproc_if"
| "preproc_elif"
| "preproc_elifdef"
| "preproc_else"
| "linkage_specification"
| "declaration_list"
| "ERROR" => return Descend::Children,
"function_definition" => {
if let Some(name) = node.child_by_field_name("declarator").and_then(innermost_identifier) {
items.push(item(StructureKind::Function, &name, node, node.end_byte(), source));
}
}
"declaration" => {
let mut cursor = node.walk();
for declarator in node.children_by_field_name("declarator", &mut cursor) {
if let Some(name) = prototype_name(&declarator) {
items.push(item(StructureKind::Function, &name, node, node.end_byte(), source));
}
}
items.extend(defined_aggregate(node, source));
}
"type_definition" => {
let mut cursor = node.walk();
for declarator in node.children_by_field_name("declarator", &mut cursor) {
if let Some(name) = innermost_identifier(declarator) {
items.push(item(
StructureKind::Other("Type".to_string()),
&name,
node,
node.end_byte(),
source,
));
}
}
items.extend(defined_aggregate(node, source));
}
"struct_specifier" | "union_specifier" | "enum_specifier" => {
items.extend(aggregate(node, source));
}
"preproc_def" | "preproc_function_def" => {
if let Some(name) = node.child_by_field_name("name") {
let label = if node.kind() == "preproc_def" {
"Constant"
} else {
"Macro"
};
items.push(item(
StructureKind::Other(label.to_string()),
&name,
node,
node.end_byte(),
source,
));
}
}
_ => {}
}
Descend::Skip
});
warn_if_truncated(truncated, "intel::c", "c");
items.sort_by_key(|item| item.span.start_byte);
items
}
fn innermost_identifier<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
let mut pending = vec![node];
while let Some(current) = pending.pop() {
match current.kind() {
"identifier" | "type_identifier" | "field_identifier" => return Some(current),
_ => {
if let Some(inner) = current.child_by_field_name("declarator") {
pending.push(inner);
continue;
}
let mut cursor = current.walk();
let children: Vec<Node<'tree>> = current
.named_children(&mut cursor)
.filter(|child| child.kind().ends_with("declarator") || child.kind().ends_with("identifier"))
.collect();
pending.extend(children.into_iter().rev());
}
}
}
None
}
fn prototype_name<'tree>(declarator: &Node<'tree>) -> Option<Node<'tree>> {
let mut current = *declarator;
loop {
match current.kind() {
"pointer_declarator" | "parenthesized_declarator" => {
current = current
.child_by_field_name("declarator")
.or_else(|| sole_declarator(¤t))?;
}
"function_declarator" => {
let inner = current.child_by_field_name("declarator")?;
match inner.kind() {
"identifier" => return Some(inner),
"parenthesized_declarator" => {
let sole = sole_declarator(&inner)?;
if sole.kind() == "identifier" {
return Some(sole);
}
current = sole;
}
_ => current = inner,
}
}
_ => return None,
}
}
}
fn sole_declarator<'tree>(node: &Node<'tree>) -> Option<Node<'tree>> {
let mut cursor = node.walk();
node.named_children(&mut cursor)
.find(|child| child.kind().ends_with("declarator") || child.kind() == "identifier")
}
fn defined_aggregate(node: &Node<'_>, source: &str) -> Option<StructureItem> {
let specifier = node.child_by_field_name("type")?;
aggregate(&specifier, source)
}
fn aggregate(node: &Node<'_>, source: &str) -> Option<StructureItem> {
let kind = match node.kind() {
"struct_specifier" => StructureKind::Struct,
"union_specifier" => StructureKind::Other("Union".to_string()),
"enum_specifier" => StructureKind::Enum,
_ => return None,
};
let name = node.child_by_field_name("name")?;
node.child_by_field_name("body")?;
let end = node
.next_sibling()
.filter(|sibling| sibling.kind() == ";")
.map_or(node.end_byte(), |terminator| terminator.end_byte());
Some(item(kind, &name, node, end, source))
}
fn item(kind: StructureKind, name: &Node<'_>, node: &Node<'_>, end: usize, source: &str) -> StructureItem {
let start = node.start_position();
let span = span_between_from(source, node.start_byte(), end, (start.row, start.column));
StructureItem {
kind,
name: Some(node_text(name, source).to_string()),
span,
..StructureItem::default()
}
}