use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CallingConvention {
Cdecl,
Stdcall,
Fastcall,
}
#[derive(Debug)]
pub struct Fn {
pub name: String,
pub library: String,
pub import_name: Option<String>,
pub params: Vec<Param>,
pub return_type: metadata::Type,
pub extern_c: bool,
pub is_variadic: bool,
pub does_not_return: bool,
pub calling_convention: Option<CallingConvention>,
}
fn convention_keyword(spelling: &str) -> Option<CallingConvention> {
match spelling {
"__stdcall" | "_stdcall" => Some(CallingConvention::Stdcall),
"__cdecl" | "_cdecl" => Some(CallingConvention::Cdecl),
"__fastcall" | "_fastcall" => Some(CallingConvention::Fastcall),
_ => None,
}
}
fn resolve_convention<'a>(
spelling: &'a str,
macro_defs: &'a HashMap<String, Vec<String>>,
visited: &mut HashSet<&'a str>,
) -> Option<CallingConvention> {
if let Some(convention) = convention_keyword(spelling) {
return Some(convention);
}
if !visited.insert(spelling) {
return None;
}
let body = macro_defs.get(spelling)?;
body.iter()
.find_map(|token| resolve_convention(token, macro_defs, visited))
}
fn detect_calling_convention(
tokens: &[(CXTokenKind, String)],
name: &str,
macro_defs: &HashMap<String, Vec<String>>,
) -> Option<CallingConvention> {
let name_idx = tokens
.iter()
.position(|(_, s)| s == name)
.filter(|&i| tokens.get(i + 1).is_some_and(|(_, s)| s == "("))?;
let candidate = &tokens.get(name_idx.checked_sub(1)?)?.1;
if candidate == ")" {
let mut depth = 0usize;
let mut i = name_idx - 1;
loop {
match tokens[i].1.as_str() {
")" => depth += 1,
"(" => {
depth -= 1;
if depth == 0 {
break;
}
}
_ => {}
}
i = i.checked_sub(1)?;
}
let macro_name = &tokens.get(i.checked_sub(1)?)?.1;
return resolve_convention(macro_name, macro_defs, &mut HashSet::new());
}
let mut i = name_idx.checked_sub(1)?;
loop {
let token = tokens[i].1.as_str();
if matches!(token, ";" | "{" | "}") {
return None;
}
if let Some(convention) = resolve_convention(token, macro_defs, &mut HashSet::new()) {
return Some(convention);
}
i = i.checked_sub(1)?;
}
}
pub(super) fn detect_callback_calling_convention(
tokens: &[(CXTokenKind, String)],
name: &str,
macro_defs: &HashMap<String, Vec<String>>,
) -> Option<CallingConvention> {
let name_idx = tokens.iter().rposition(|(_, s)| s == name)?;
let mut i = name_idx.checked_sub(1)?;
loop {
let token = tokens[i].1.as_str();
if matches!(token, ";" | "{" | "}") || token == "typedef" {
return None;
}
if let Some(convention) = resolve_convention(token, macro_defs, &mut HashSet::new()) {
return Some(convention);
}
i = i.checked_sub(1)?;
}
}
fn token_names_function(tokens: &[(CXTokenKind, String)], name: &str) -> bool {
tokens
.iter()
.position(|(_, s)| s == name)
.is_some_and(|i| tokens.get(i + 1).is_some_and(|(_, s)| s == "("))
}
impl Fn {
pub fn parse(cursor: Cursor, parser: &mut Parser<'_>, extern_c: bool) -> Result<Self, Error> {
let export_name = cursor.name();
let return_type = cursor.result_type().to_type(parser);
let is_variadic = cursor.ty().is_variadic();
let does_not_return = detect_does_not_return(&cursor);
let fn_tokens = parser
.tu
.tokenize(parser.tu.to_expansion_range(cursor.extent()));
let source_name = parser
.alias_map
.get(&export_name)
.filter(|alias| {
token_names_function(&fn_tokens, alias)
&& !token_names_function(&fn_tokens, &export_name)
})
.cloned();
let anchor = source_name.as_deref().unwrap_or(&export_name);
let midl_annotations = scan_method_param_annotations(&fn_tokens, anchor, parser.macro_defs);
let calling_convention = detect_calling_convention(&fn_tokens, anchor, parser.macro_defs);
let mut params = parse_params(&cursor, &midl_annotations, parser);
infer_iid_is(&mut params, &return_type);
let library = parser
.libraries
.get(&export_name)
.cloned()
.unwrap_or_else(|| parser.library.to_string());
let (name, import_name) = match source_name {
Some(source) => (source, Some(export_name)),
None => (export_name, None),
};
Ok(Self {
name,
library,
import_name,
params,
return_type,
extern_c,
is_variadic,
does_not_return,
calling_convention,
})
}
pub fn write(&self, namespace: &str) -> Result<TokenStream, Error> {
let name = write_ident(&self.name);
let library = &self.library;
let mut params: Vec<TokenStream> = self
.params
.iter()
.map(|param| {
let name = write_ident(¶m.name);
let ty = write_type(namespace, ¶m.ty);
let attrs = param_attrs_for_annotation(¶m.annotation, ¶m.ty);
quote! { #(#attrs)* #name: #ty }
})
.collect();
if self.is_variadic {
params.push(quote! { ... });
}
let return_type = match &self.return_type {
metadata::Type::Void => quote! {},
ty => {
let ty = write_type(namespace, ty);
quote! { -> #ty }
}
};
let abi = if self.is_variadic {
quote! { "C" }
} else {
match self.calling_convention {
Some(CallingConvention::Stdcall) => quote! { "system" },
Some(CallingConvention::Cdecl) => quote! { "C" },
Some(CallingConvention::Fastcall) => quote! { "fastcall" },
None if self.extern_c => quote! { "C" },
None => quote! {},
}
};
let does_not_return = if self.does_not_return {
does_not_return_attr()
} else {
quote! {}
};
let library_attr = if let Some(import) = &self.import_name {
quote! { #[library(#library, import = #import)] }
} else {
quote! { #[library(#library)] }
};
Ok(quote! {
#does_not_return
#library_attr
extern #abi fn #name(#(#params),*) #return_type;
})
}
}