use super::*;
#[derive(Clone, Copy)]
pub(crate) enum MacroSource<'a> {
File(&'a str),
Str(&'a str),
}
pub(crate) fn evaluate_macros_parallel(
all_consts: &[(String, Vec<String>)],
source: MacroSource<'_>,
args: &[&str],
) -> Result<Vec<Vec<Const>>, Error> {
let n = all_consts.len();
if n == 0 {
return Ok(vec![]);
}
let mut seen = HashSet::new();
let mut union: Vec<String> = vec![];
for (_, names) in all_consts {
for name in names {
if seen.insert(name.as_str()) {
union.push(name.clone());
}
}
}
let evaluated_union: Vec<Const> = if union.is_empty() {
vec![]
} else {
let workers = std::thread::available_parallelism()
.map_or(1, |p| p.get())
.min(union.len());
let chunk_size = union.len().div_ceil(workers);
std::thread::scope(|scope| -> Result<Vec<Const>, Error> {
let handles: Vec<_> = union
.chunks(chunk_size)
.map(|chunk| {
scope.spawn(move || -> Result<Vec<Const>, Error> {
let _library = Library::new()?;
let index = Index::new()?;
match source {
MacroSource::File(input) => {
Const::evaluate_macros(input, chunk, &index, args)
}
MacroSource::Str(content) => {
Const::evaluate_macros_str(content, chunk, &index, args)
}
}
})
})
.collect();
let mut all = vec![];
for handle in handles {
all.extend(
handle
.join()
.map_err(|_| Error::new("macro evaluation worker panicked", "", 0, 0))??,
);
}
Ok(all)
})?
};
let mut map: HashMap<String, Const> = evaluated_union
.into_iter()
.map(|c| (c.name.clone(), c))
.collect();
let mut out: Vec<Vec<Const>> = Vec::with_capacity(n);
for (_, names) in all_consts {
let mut consts = vec![];
for name in names {
if let Some(c) = map.remove(name) {
consts.push(c);
}
}
out.push(consts);
}
Ok(out)
}
#[derive(Clone, Copy)]
pub(crate) struct MacroEval<'a> {
pub(crate) source: MacroSource<'a>,
pub(crate) args: &'a [&'a str],
}
pub(crate) fn is_type_keyword(spelling: &str) -> bool {
matches!(
spelling,
"int"
| "long"
| "short"
| "char"
| "unsigned"
| "signed"
| "bool"
| "wchar_t"
| "__int8"
| "__int16"
| "__int32"
| "__int64"
)
}
pub(crate) fn tokens_balanced<'a>(tokens: impl Iterator<Item = &'a (CXTokenKind, String)>) -> bool {
let mut stack: Vec<char> = vec![];
for (kind, spelling) in tokens {
if *kind == CXToken_Literal || *kind == CXToken_Comment {
continue;
}
for ch in spelling.chars() {
match ch {
'(' => stack.push(')'),
'[' => stack.push(']'),
'{' => stack.push('}'),
')' | ']' | '}' if stack.pop() != Some(ch) => return false,
_ => {}
}
}
}
stack.is_empty()
}
pub(crate) fn collect_macro_defs(tu: &TranslationUnit) -> HashMap<String, Vec<String>> {
let mut defs = HashMap::new();
for child in tu.cursor().children() {
if child.kind() != CXCursor_MacroDefinition || child.is_macro_builtin() {
continue;
}
let name = child.name();
if name.is_empty() {
continue;
}
let tokens = tu.tokenize(child.extent());
let mut body: Vec<String> = tokens.into_iter().skip(1).map(|(_, s)| s).collect();
if child.is_macro_function_like() && body.first().map(String::as_str) == Some("(") {
let mut depth = 0usize;
let mut end = None;
for (idx, token) in body.iter().enumerate() {
match token.as_str() {
"(" => depth += 1,
")" => {
depth -= 1;
if depth == 0 {
end = Some(idx);
break;
}
}
_ => {}
}
}
if let Some(end) = end {
body.drain(0..=end);
}
}
strip_declspec(&mut body);
if body.len() <= 4 {
defs.insert(name, body);
}
}
defs
}
pub(crate) fn build_alias_map(
macro_defs: &HashMap<String, Vec<String>>,
) -> HashMap<String, String> {
let mut map: HashMap<String, String> = HashMap::new();
for (alias, body) in macro_defs {
let [export] = body.as_slice() else {
continue;
};
if export == alias || !is_c_identifier(export) {
continue;
}
if *export == format!("{alias}A") || *export == format!("{alias}W") {
continue;
}
map.entry(export.clone())
.and_modify(|current| {
if alias < current {
current.clone_from(alias);
}
})
.or_insert_with(|| alias.clone());
}
map
}
pub(crate) fn is_c_identifier(s: &str) -> bool {
let mut chars = s.chars();
chars
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
pub(crate) fn strip_declspec(body: &mut Vec<String>) {
let mut i = 0;
while i < body.len() {
if matches!(body[i].as_str(), "__declspec" | "_declspec")
&& body.get(i + 1).map(String::as_str) == Some("(")
{
let mut depth = 0usize;
let mut end = None;
for (idx, token) in body.iter().enumerate().skip(i + 1) {
match token.as_str() {
"(" => depth += 1,
")" => {
depth -= 1;
if depth == 0 {
end = Some(idx);
break;
}
}
_ => {}
}
}
if let Some(end) = end {
body.drain(i..=end);
continue;
}
}
i += 1;
}
}