Last active
September 7, 2022 20:41
-
-
Save limads/a73f0b8a44cb1c59debabcf8ea43e65c to your computer and use it in GitHub Desktop.
Procedural macro to describe Rust API in dynamic libraries
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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