use std::fmt::Write;
use super::ir::{Function, Header, Item, Type};
#[derive(Debug, Clone)]
pub struct Options {
pub struct_name: String,
}
const KEYWORDS: &[&str] = &[
"as", "async", "await", "box", "break", "const", "continue", "crate", "dyn", "else", "enum",
"extern", "false", "fn", "for", "gen", "if", "impl", "in", "let", "loop", "match", "mod",
"move", "mut", "priv", "pub", "ref", "return", "self", "Self", "static", "struct", "super",
"trait", "true", "try", "type", "unsafe", "use", "where", "while", "yield",
];
pub fn emit(header: &Header, opts: &Options) -> String {
let mut out = String::new();
out.push_str("// @generated by dlsym-gen. Do not edit by hand.\n\n");
let _ = writeln!(out, "::dlsym::library! {{");
let _ = writeln!(out, " pub struct {} {{", opts.struct_name);
for item in &header.items {
match item {
Item::Function(f) if f.variadic => {
let _ = writeln!(out, " // skipped variadic function `{}`", f.name);
}
Item::Function(f) => emit_function(&mut out, f),
}
}
out.push_str(" }\n}\n");
out
}
fn emit_function(out: &mut String, f: &Function) {
if let Some(doc) = &f.doc {
for line in doc.lines() {
let _ = writeln!(out, " /// {}", line.trim_end());
}
}
let params: Vec<String> = f
.params
.iter()
.enumerate()
.map(|(i, p)| {
let name = match &p.name {
Some(n) => ident(n),
None => format!("arg{i}"),
};
format!("{name}: {}", rust_type(&p.ty))
})
.collect();
let ret = match f.ret {
Type::Void => String::new(),
ref ty => format!(" -> {}", rust_type(ty)),
};
let _ = writeln!(
out,
" pub fn {}({}){ret};",
f.name,
params.join(", ")
);
}
fn ident(name: &str) -> String {
match name {
"self" | "Self" | "super" | "crate" | "_" => format!("{name}_"),
n if KEYWORDS.contains(&n) => format!("r#{n}"),
n => n.to_owned(),
}
}
fn rust_type(ty: &Type) -> String {
let ffi = |name: &str| format!("::core::ffi::{name}");
match ty {
Type::Void => "()".into(),
Type::Bool => "bool".into(),
Type::Char => ffi("c_char"),
Type::SChar => ffi("c_schar"),
Type::UChar => ffi("c_uchar"),
Type::Short => ffi("c_short"),
Type::UShort => ffi("c_ushort"),
Type::Int => ffi("c_int"),
Type::UInt => ffi("c_uint"),
Type::Long => ffi("c_long"),
Type::ULong => ffi("c_ulong"),
Type::LongLong => ffi("c_longlong"),
Type::ULongLong => ffi("c_ulonglong"),
Type::SizeT => "usize".into(),
Type::SSizeT => "isize".into(),
Type::Float => "f32".into(),
Type::Double => "f64".into(),
Type::Pointer { pointee, is_const } => {
let qual = if *is_const { "const" } else { "mut" };
let inner = match **pointee {
Type::Void => ffi("c_void"),
ref t => rust_type(t),
};
format!("*{qual} {inner}")
}
Type::Named(name) => ident(name),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::ir::Param;
fn param(name: &str, ty: Type) -> Param {
Param {
name: Some(name.into()),
ty,
}
}
#[test]
fn emits_library_macro() {
let header = Header {
items: vec![
Item::Function(Function {
name: "cos".into(),
params: vec![param("x", Type::Double)],
ret: Type::Double,
variadic: false,
doc: Some("Cosine of `x`.".into()),
}),
Item::Function(Function {
name: "memcpy".into(),
params: vec![
param("dest", Type::ptr(Type::Void)),
param("src", Type::const_ptr(Type::Void)),
Param {
name: None,
ty: Type::SizeT,
},
],
ret: Type::ptr(Type::Void),
variadic: false,
doc: None,
}),
Item::Function(Function {
name: "set_type".into(),
params: vec![param("type", Type::Int)],
ret: Type::Void,
variadic: false,
doc: None,
}),
Item::Function(Function {
name: "printf".into(),
params: vec![param("fmt", Type::const_ptr(Type::Char))],
ret: Type::Int,
variadic: true,
doc: None,
}),
],
};
let out = emit(
&header,
&Options {
struct_name: "Demo".into(),
},
);
assert_eq!(
out,
"\
// @generated by dlsym-gen. Do not edit by hand.
::dlsym::library! {
pub struct Demo {
/// Cosine of `x`.
pub fn cos(x: f64) -> f64;
pub fn memcpy(dest: *mut ::core::ffi::c_void, src: *const ::core::ffi::c_void, arg2: usize) -> *mut ::core::ffi::c_void;
pub fn set_type(r#type: ::core::ffi::c_int);
// skipped variadic function `printf`
}
}
"
);
}
}