Skip to content

Instantly share code, notes, and snippets.

@limads
Last active September 7, 2022 20:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save limads/a73f0b8a44cb1c59debabcf8ea43e65c to your computer and use it in GitHub Desktop.
Save limads/a73f0b8a44cb1c59debabcf8ea43e65c to your computer and use it in GitHub Desktop.
Procedural macro to describe Rust API in dynamic libraries
use proc_macro::TokenStream;
use syn::{parse_macro_input};
use serde::{Serialize, Deserialize};
use quote::ToTokens;
use std::str::FromStr;
/* Example usage:
#[export_abi]
mod {
#[no_mangle]
extern "C" fn a(a : i32) {
0
}
#[no_mangle]
extern "C" fn b(b : i64) {
0
}
}
// Generated code
#[no_mangle]
static API : &'static str = "{
\"a\" : { args : [\"i32\"],
\"b\" : { \"args\" : [\"i64\"] }
}";
```
*/
#[derive(Deserialize, Serialize)]
struct Function {
name : String,
args : Vec<String>,
ret : Option<String>
}
#[proc_macro_attribute]
pub fn export_abi(_attr: TokenStream, item: TokenStream) -> TokenStream {
// let mut out = TokenStream::from(input);
let mut out = item.clone();
let input = parse_macro_input!(item);
let mut funcs = Vec::new();
match input {
syn::Item::Mod(ref m) => {
if let Some((_, ref items)) = m.content {
for it in items {
match it {
syn::Item::Fn(f) => {
let mut add_this = false;
for attr in &f.attrs {
if let Some(l) = attr.path.segments.last() {
if &l.ident.to_string()[..] == "no_mangle" {
add_this = true;
break;
}
}
}
if let Some(abi) = &f.sig.abi {
if let Some(abi_name) = &abi.name {
if &abi_name.value()[..] != "C" {
add_this = false;
}
} else {
add_this = false;
}
} else {
add_this = false;
}
if !add_this {
continue;
}
let name = f.sig.ident.to_string();
let mut args = Vec::new();
for input in f.sig.inputs.iter() {
match input {
syn::FnArg::Typed(pat_ty) => {
args.push(format!("{}", pat_ty.ty.to_token_stream()));
},
_ => {
panic!()
}
}
}
let ret = match &f.sig.output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => {
Some(format!("{}", ty.to_token_stream()))
}
};
funcs.push(Function { name, args, ret });
},
_ => {
}
}
}
}
},
_ => { }
}
let content = serde_json::to_string(&funcs).unwrap();
let expanded = TokenStream::from_str(&format!("
#[no_mangle]\n
static API : &'static str = r#\"{}\"#;
", content)).unwrap();
// Hand the output tokens back to the compiler
out.extend(TokenStream::from(expanded));
out
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment