use std::path::{Path, PathBuf};
use clang::{Entity, EntityKind, EntityVisitResult, Index};
use crate::Saja;
impl Saja {
pub fn extract(&self, source: &Path, target: &Path) -> anyhow::Result<Vec<Export>> {
let index = Index::new(&self.clang, false, false);
let tu = index
.parser(source)
.arguments(self.profiles.get("syntax").unwrap())
.parse()?;
let mut exports = vec![];
tu.get_entity().visit_children(|entity, _parent| {
let is_public = entity.get_children().iter().any(|child| {
child.get_kind() == EntityKind::AnnotateAttr
&& child.get_display_name().is_some_and(|n| n == "public")
});
if is_public && let Some(name) = entity.get_name() {
if let Ok(decl) = Declaration::try_from(entity) {
exports.push(Export {
name,
module: source
.strip_prefix(target.join("src"))
.unwrap()
.to_path_buf(),
decl,
});
} else {
return EntityVisitResult::Continue;
}
}
EntityVisitResult::Continue
});
Ok(exports)
}
}
#[derive(Debug)]
pub enum Declaration {
Struct,
Enum,
Union,
Function {
return_type: String,
params: Vec<(String, String)>,
},
Typedef {
underlying: String,
source: String,
},
Variable {
ty: String,
},
}
impl<'a> TryFrom<Entity<'a>> for Declaration {
type Error = ();
fn try_from(entity: Entity) -> Result<Self, Self::Error> {
match entity.get_kind() {
EntityKind::StructDecl => Ok(Declaration::Struct),
EntityKind::EnumDecl => Ok(Declaration::Enum),
EntityKind::UnionDecl => Ok(Declaration::Union),
EntityKind::FunctionDecl => {
let result_ty = entity
.get_result_type()
.map(|t| t.get_display_name())
.unwrap_or("void".into());
let params = entity
.get_arguments()
.unwrap_or_default()
.into_iter()
.map(|arg| {
(
arg.get_type()
.map(|t| t.get_display_name())
.unwrap_or_default(),
arg.get_name().unwrap_or_default(),
)
})
.collect();
Ok(Declaration::Function {
return_type: result_ty,
params,
})
}
EntityKind::TypedefDecl => {
let underlying = entity
.get_typedef_underlying_type()
.map(|t| t.get_display_name())
.unwrap_or_default();
let source = entity
.get_range()
.map(|range| {
range
.tokenize()
.into_iter()
.map(|token| token.get_spelling())
.filter(|token| token != "public")
.collect::<Vec<_>>()
.join(" ")
})
.unwrap_or_default();
Ok(Declaration::Typedef { underlying, source })
}
EntityKind::VarDecl => {
let ty = entity
.get_type()
.map(|t| t.get_display_name())
.unwrap_or_default();
Ok(Declaration::Variable { ty })
}
_ => Err(()),
}
}
}
#[derive(Debug)]
pub struct Export {
pub name: String,
pub module: PathBuf,
pub decl: Declaration,
}
impl Export {
fn format_param(ty: &str, name: &str) -> String {
if let Some((base, suffix)) = ty.split_once('[') {
return format!("{base} {name}[{suffix}");
}
format!("{ty} {name}")
}
pub fn forward(&self) -> String {
match &self.decl {
Declaration::Struct => format!("struct {};", self.name),
Declaration::Enum => format!("enum {};", self.name),
Declaration::Union => format!("union {};", self.name),
Declaration::Function {
return_type,
params,
} => {
let params = params
.iter()
.map(|(ty, name)| Self::format_param(ty, name))
.collect::<Vec<_>>()
.join(", ");
format!("{return_type} {}({params});", self.name)
}
Declaration::Typedef { underlying, source } if source.is_empty() => {
format!("typedef {underlying} {};", self.name)
}
Declaration::Typedef { source, .. } => {
format!("{source};")
}
Declaration::Variable { ty } => {
format!("extern {ty} {};", self.name)
}
}
}
}