use tree_sitter::Node;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgRole {
Iterator,
Temp,
Out,
Other,
}
pub fn macro_arg_roles(name: &str) -> Option<&'static [ArgRole]> {
use ArgRole::*;
Some(match name {
"LL_FOREACH" | "DL_FOREACH" | "CDL_FOREACH" | "LL_FOREACH2" | "DL_FOREACH2"
| "CDL_FOREACH2" => &[Other, Iterator],
"LL_FOREACH_SAFE" | "DL_FOREACH_SAFE" | "LL_FOREACH_SAFE2" | "DL_FOREACH_SAFE2" => {
&[Other, Iterator, Temp]
}
"CDL_FOREACH_SAFE" | "CDL_FOREACH_SAFE2" => &[Other, Iterator, Temp, Temp],
"LL_SEARCH_SCALAR" | "DL_SEARCH_SCALAR" | "CDL_SEARCH_SCALAR" | "LL_SEARCH"
| "DL_SEARCH" | "CDL_SEARCH" | "LL_SEARCH_SCALAR2" | "DL_SEARCH_SCALAR2"
| "CDL_SEARCH_SCALAR2" => &[Other, Out],
"HASH_ITER" => &[Other, Other, Iterator, Temp],
"TAILQ_FOREACH" | "LIST_FOREACH" | "SLIST_FOREACH" | "STAILQ_FOREACH"
| "SIMPLEQ_FOREACH" | "CIRCLEQ_FOREACH" => &[Iterator],
"TAILQ_FOREACH_SAFE"
| "LIST_FOREACH_SAFE"
| "SLIST_FOREACH_SAFE"
| "STAILQ_FOREACH_SAFE" => &[Iterator, Other, Other, Temp],
"RB_FOREACH" => &[Iterator],
"dl_list_for_each"
| "dl_list_for_each_safe"
| "dl_list_for_each_reverse"
| "for_each_link" => &[Iterator],
_ => return None,
})
}
pub fn is_hash_find_family(name: &str) -> bool {
name.starts_with("HASH_FIND") || name.starts_with("HASH_REPLACE")
}
pub fn is_registered(name: &str) -> bool {
macro_arg_roles(name).is_some() || is_hash_find_family(name)
}
fn arg_role_at(name: &str, pos: usize, nargs: usize) -> ArgRole {
if let Some(roles) = macro_arg_roles(name) {
return roles.get(pos).copied().unwrap_or(ArgRole::Other);
}
if is_hash_find_family(name) && nargs > 0 && pos == nargs - 1 {
return ArgRole::Out;
}
ArgRole::Other
}
pub fn positional_args<'a>(call: &Node<'a>) -> Vec<Node<'a>> {
let mut out = Vec::new();
if let Some(args) = call.child_by_field_name("arguments") {
for i in 0..args.child_count() {
if let Some(arg) = args.child(i) {
if matches!(arg.kind(), "(" | ")" | ",") {
continue;
}
out.push(arg);
}
}
}
out
}
pub fn output_var_args(call: &Node, source: &str) -> Vec<(String, ArgRole)> {
let func_name = match call.child_by_field_name("function") {
Some(f) => f.utf8_text(source.as_bytes()).unwrap_or("").to_string(),
None => return Vec::new(),
};
if !is_registered(&func_name) {
return Vec::new();
}
let args = positional_args(call);
let nargs = args.len();
let mut out = Vec::new();
for (pos, arg) in args.into_iter().enumerate() {
let role = arg_role_at(&func_name, pos, nargs);
if matches!(role, ArgRole::Iterator | ArgRole::Temp | ArgRole::Out)
&& arg.kind() == "identifier"
{
let name = arg.utf8_text(source.as_bytes()).unwrap_or("").to_string();
if !name.is_empty() {
out.push((name, role));
}
}
}
out
}
pub fn is_macro_output_arg(ident: &Node, source: &str) -> bool {
if ident.kind() != "identifier" {
return false;
}
let arg_list = match ident.parent() {
Some(p) if p.kind() == "argument_list" => p,
_ => return false,
};
let call = match arg_list.parent() {
Some(c) if c.kind() == "call_expression" => c,
_ => return false,
};
let func_name = match call.child_by_field_name("function") {
Some(f) => f.utf8_text(source.as_bytes()).unwrap_or(""),
None => return false,
};
if !is_registered(func_name) {
return false;
}
let args = positional_args(&call);
let nargs = args.len();
let target = ident.id();
for (pos, arg) in args.into_iter().enumerate() {
if arg.id() == target {
let role = arg_role_at(func_name, pos, nargs);
return matches!(role, ArgRole::Iterator | ArgRole::Temp | ArgRole::Out);
}
}
false
}
pub fn is_in_iterator_macro_body(node: &Node, source: &str, var: &str) -> bool {
let mut cur = *node;
while let Some(parent) = cur.parent() {
if cur.kind() == "compound_statement" {
if let Some(prev) = cur.prev_named_sibling() {
if iterator_macro_call_for(&prev, source, var) {
return true;
}
}
}
cur = parent;
}
false
}
fn iterator_macro_call_for(stmt: &Node, source: &str, var: &str) -> bool {
let call = if stmt.kind() == "call_expression" {
*stmt
} else if stmt.kind() == "expression_statement" {
match stmt.child(0) {
Some(c) if c.kind() == "call_expression" => c,
_ => return false,
}
} else {
return false;
};
let func_name = match call.child_by_field_name("function") {
Some(f) => f.utf8_text(source.as_bytes()).unwrap_or(""),
None => return false,
};
let roles = match macro_arg_roles(func_name) {
Some(r) => r,
None => return false,
};
for (pos, arg) in positional_args(&call).into_iter().enumerate() {
if roles.get(pos).copied() == Some(ArgRole::Iterator)
&& arg.kind() == "identifier"
&& arg.utf8_text(source.as_bytes()).unwrap_or("") == var
{
return true;
}
}
false
}