use crate::graph::types::FfiAbi;
#[cfg(any(
feature = "rust",
feature = "c",
feature = "cpp",
feature = "python",
feature = "typescript",
feature = "java"
))]
pub(crate) struct AbiSpec {
pub abi: FfiAbi,
pub consumers: &'static [&'static str],
#[cfg(feature = "rust")]
pub rust_attr_markers: &'static [&'static str],
#[cfg(feature = "rust")]
pub rust_name_override_markers: &'static [&'static str],
#[cfg(any(feature = "rust", feature = "c"))]
pub name_prefix: Option<&'static str>,
}
#[cfg(any(
feature = "rust",
feature = "c",
feature = "cpp",
feature = "python",
feature = "typescript",
feature = "java"
))]
pub(crate) const SPECS: &[AbiSpec] = &[
#[cfg(any(feature = "rust", feature = "c", feature = "cpp"))]
super::c::SPEC,
#[cfg(any(feature = "rust", feature = "python"))]
super::python::SPEC,
#[cfg(any(feature = "rust", feature = "typescript"))]
super::wasm::SPEC,
#[cfg(any(feature = "rust", feature = "typescript"))]
super::node_api::SPEC,
#[cfg(any(feature = "rust", feature = "c", feature = "java"))]
super::jni::SPEC,
];
#[cfg(any(
feature = "rust",
feature = "c",
feature = "cpp",
feature = "python",
feature = "typescript",
feature = "java"
))]
pub(crate) fn consumers(abi: FfiAbi) -> &'static [&'static str] {
SPECS
.iter()
.find(|s| s.abi == abi)
.map_or(&[], |s| s.consumers)
}
#[cfg(not(any(
feature = "rust",
feature = "c",
feature = "cpp",
feature = "python",
feature = "typescript",
feature = "java"
)))]
pub(crate) fn consumers(_: FfiAbi) -> &'static [&'static str] {
&[]
}
#[cfg(feature = "rust")]
fn reclassify_by_name(base: FfiAbi, name: &str) -> FfiAbi {
c_name_export_abi(name).unwrap_or(base)
}
#[cfg(any(feature = "rust", feature = "c"))]
pub(crate) fn c_name_export_abi(name: &str) -> Option<FfiAbi> {
SPECS
.iter()
.find(|s| s.name_prefix.is_some_and(|p| name.starts_with(p)))
.map(|s| s.abi)
}
#[cfg(feature = "rust")]
pub(crate) fn rust_exports(attr_texts: &[&str], fn_name: &str) -> Vec<(FfiAbi, String)> {
let mut out = Vec::new();
for spec in SPECS {
if spec.rust_attr_markers.is_empty() {
continue;
}
let enabled = attr_texts.iter().any(|t| {
spec.rust_attr_markers
.iter()
.any(|marker| exact_rust_attribute(t, marker))
});
if !enabled {
continue;
}
let name = attr_texts
.iter()
.rev()
.filter(|t| {
spec.rust_name_override_markers
.iter()
.any(|marker| exact_rust_attribute_assignment(t, marker))
})
.find_map(|t| attribute_assignment_value(t))
.map(str::to_owned)
.unwrap_or_else(|| fn_name.to_owned());
out.push((reclassify_by_name(spec.abi, &name), name));
}
out
}
#[cfg(feature = "rust")]
fn exact_rust_attribute(text: &str, marker: &str) -> bool {
let body = text
.trim()
.strip_prefix("#[")
.and_then(|s| s.strip_suffix(']'))
.map(str::trim);
let Some(body) = body else {
return false;
};
body == marker
|| body
.split_once('=')
.is_some_and(|(key, _)| key.trim() == marker)
|| body
.strip_prefix("unsafe(")
.and_then(|s| s.strip_suffix(')'))
.is_some_and(|inner| inner.trim() == marker)
|| body
.strip_prefix(marker)
.is_some_and(|tail| tail.starts_with('('))
}
#[cfg(feature = "rust")]
fn exact_rust_attribute_assignment(text: &str, marker: &str) -> bool {
let body = text
.trim()
.strip_prefix("#[")
.and_then(|s| s.strip_suffix(']'))
.map(str::trim);
body.is_some_and(|body| {
body.split_once('=')
.is_some_and(|(key, _)| key.trim() == marker)
|| body
.strip_prefix(marker)
.is_some_and(|tail| tail.starts_with('('))
|| body
.split_once('(')
.and_then(|(_, args)| args.strip_suffix(')'))
.is_some_and(|args| {
args.split(',').any(|arg| {
arg.split_once('=')
.is_some_and(|(key, _)| key.trim() == marker)
})
})
})
}
#[cfg(feature = "rust")]
fn attribute_assignment_value(text: &str) -> Option<&str> {
let body = text.trim().strip_prefix("#[")?.strip_suffix(']')?.trim();
let value = if let Some((_, args)) = body.split_once('(') {
args.strip_suffix(')')?
.split(',')
.find_map(|arg| arg.split_once('=').map(|(_, value)| value))?
} else {
let (_, value) = body.split_once('=')?;
value
};
value.trim().strip_prefix('"')?.strip_suffix('"')
}