mirror of
https://git.proxmox.com/git/perlmod
synced 2025-10-05 22:12:57 +00:00
macro: add export macro, support renaming xs functions
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
52f7cd49f1
commit
abdf721d90
85
perlmod-macro/src/attribs.rs
Normal file
85
perlmod-macro/src/attribs.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
|
||||||
|
use syn::AttributeArgs;
|
||||||
|
|
||||||
|
pub struct ModuleAttrs {
|
||||||
|
pub package_name: String,
|
||||||
|
pub file_name: String,
|
||||||
|
pub lib_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<AttributeArgs> for ModuleAttrs {
|
||||||
|
type Error = syn::Error;
|
||||||
|
|
||||||
|
fn try_from(args: AttributeArgs) -> Result<Self, Self::Error> {
|
||||||
|
let mut package_name = None;
|
||||||
|
let mut file_name = None;
|
||||||
|
let mut lib_name = None;
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
match arg {
|
||||||
|
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||||
|
path,
|
||||||
|
eq_token: _,
|
||||||
|
lit: syn::Lit::Str(litstr),
|
||||||
|
})) => {
|
||||||
|
if path.is_ident("name") {
|
||||||
|
package_name = Some(litstr.value());
|
||||||
|
} else if path.is_ident("file") {
|
||||||
|
file_name = Some(litstr.value());
|
||||||
|
} else if path.is_ident("lib") {
|
||||||
|
lib_name = Some(litstr.value());
|
||||||
|
} else {
|
||||||
|
bail!(path => "unknown argument");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => bail!(Span::call_site(), "unexpected attribute argument"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let package_name = package_name
|
||||||
|
.ok_or_else(|| format_err!(Span::call_site(), "missing 'package' argument"))?;
|
||||||
|
|
||||||
|
let file_name =
|
||||||
|
file_name.unwrap_or_else(|| format!("{}.pm", package_name.replace("::", "/")));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
package_name,
|
||||||
|
file_name,
|
||||||
|
lib_name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FunctionAttrs {
|
||||||
|
pub xs_name: Option<Ident>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<AttributeArgs> for FunctionAttrs {
|
||||||
|
type Error = syn::Error;
|
||||||
|
|
||||||
|
fn try_from(args: AttributeArgs) -> Result<Self, Self::Error> {
|
||||||
|
let mut xs_name = None;
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
match arg {
|
||||||
|
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||||
|
path,
|
||||||
|
eq_token: _,
|
||||||
|
lit: syn::Lit::Str(litstr),
|
||||||
|
})) => {
|
||||||
|
if path.is_ident("name") {
|
||||||
|
xs_name = Some(Ident::new(&litstr.value(), litstr.span()));
|
||||||
|
} else {
|
||||||
|
bail!(path => "unknown argument");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => bail!(Span::call_site(), "unexpected attribute argument"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { xs_name })
|
||||||
|
}
|
||||||
|
}
|
@ -2,15 +2,18 @@ extern crate proc_macro;
|
|||||||
extern crate proc_macro2;
|
extern crate proc_macro2;
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::iter::IntoIterator;
|
||||||
|
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
|
||||||
use proc_macro::TokenStream as TokenStream_1;
|
use proc_macro::TokenStream as TokenStream_1;
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
use proc_macro2::{Ident, TokenStream};
|
||||||
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::parse::Parser;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
use syn::AttributeArgs;
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{AttributeArgs, Token};
|
||||||
|
|
||||||
macro_rules! format_err {
|
macro_rules! format_err {
|
||||||
($span:expr => $($msg:tt)*) => { syn::Error::new_spanned($span, format!($($msg)*)) };
|
($span:expr => $($msg:tt)*) => { syn::Error::new_spanned($span, format!($($msg)*)) };
|
||||||
@ -22,6 +25,8 @@ macro_rules! bail {
|
|||||||
($span:expr, $($msg:tt)*) => { return Err(format_err!($span, $($msg)*).into()) };
|
($span:expr, $($msg:tt)*) => { return Err(format_err!($span, $($msg)*).into()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod attribs;
|
||||||
|
|
||||||
fn handle_error(mut item: TokenStream, data: Result<TokenStream, Error>) -> TokenStream {
|
fn handle_error(mut item: TokenStream, data: Result<TokenStream, Error>) -> TokenStream {
|
||||||
match data {
|
match data {
|
||||||
Ok(output) => output,
|
Ok(output) => output,
|
||||||
@ -41,7 +46,7 @@ struct XSub {
|
|||||||
tokens: TokenStream,
|
tokens: TokenStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro for exporting rust functions as perl xsubs.
|
/// Macro for starting a perl "package".
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn package(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 {
|
pub fn package(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 {
|
||||||
let attr = parse_macro_input!(attr as AttributeArgs);
|
let attr = parse_macro_input!(attr as AttributeArgs);
|
||||||
@ -49,20 +54,33 @@ pub fn package(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 {
|
|||||||
handle_error(item.clone(), perlmod_impl(attr, item)).into()
|
handle_error(item.clone(), perlmod_impl(attr, item)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Macro to generate an exported xsub for a function.
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn export(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 {
|
||||||
|
let attr = parse_macro_input!(attr as AttributeArgs);
|
||||||
|
let item: TokenStream = item.into();
|
||||||
|
handle_error(item.clone(), export_impl(attr, item)).into()
|
||||||
|
}
|
||||||
|
|
||||||
fn perlmod_impl(attr: AttributeArgs, item: TokenStream) -> Result<TokenStream, Error> {
|
fn perlmod_impl(attr: AttributeArgs, item: TokenStream) -> Result<TokenStream, Error> {
|
||||||
let item: syn::Item = syn::parse2(item)?;
|
let item: syn::Item = syn::parse2(item)?;
|
||||||
|
|
||||||
match item {
|
match item {
|
||||||
syn::Item::Fn(func) => {
|
syn::Item::Fn(func) => bail!(func => "did you mean to use the 'export' macro?"),
|
||||||
let func = handle_function(func)?;
|
|
||||||
Ok(func.tokens)
|
|
||||||
}
|
|
||||||
syn::Item::Mod(module) => handle_module(attr, module),
|
syn::Item::Mod(module) => handle_module(attr, module),
|
||||||
_ => bail!(item => "expected module or function"),
|
_ => bail!(item => "expected module or function"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_function(func: syn::ItemFn) -> Result<XSub, Error> {
|
fn export_impl(attr: AttributeArgs, item: TokenStream) -> Result<TokenStream, Error> {
|
||||||
|
let func: syn::ItemFn = syn::parse2(item)?;
|
||||||
|
|
||||||
|
let attr = attribs::FunctionAttrs::try_from(attr)?;
|
||||||
|
let func = handle_function(attr, func)?;
|
||||||
|
Ok(func.tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_function(attr: attribs::FunctionAttrs, func: syn::ItemFn) -> Result<XSub, Error> {
|
||||||
//let vis = core::mem::replace(&mut func.vis, syn::Visibility::Inherited);
|
//let vis = core::mem::replace(&mut func.vis, syn::Visibility::Inherited);
|
||||||
//if let syn::Visibility::Public(_) = vis {
|
//if let syn::Visibility::Public(_) = vis {
|
||||||
// // ok
|
// // ok
|
||||||
@ -80,7 +98,9 @@ fn handle_function(func: syn::ItemFn) -> Result<XSub, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = &sig.ident;
|
let name = &sig.ident;
|
||||||
let xs_name = Ident::new(&format!("xs_{}", name), name.span());
|
let xs_name = attr
|
||||||
|
.xs_name
|
||||||
|
.unwrap_or_else(|| Ident::new(&format!("xs_{}", name), name.span()));
|
||||||
let impl_xs_name = Ident::new(&format!("impl_xs_{}", name), name.span());
|
let impl_xs_name = Ident::new(&format!("impl_xs_{}", name), name.span());
|
||||||
|
|
||||||
let mut extract_arguments = TokenStream::new();
|
let mut extract_arguments = TokenStream::new();
|
||||||
@ -235,57 +255,8 @@ BEGIN {
|
|||||||
|
|
||||||
const MODULE_TAIL: &str = "}\n";
|
const MODULE_TAIL: &str = "}\n";
|
||||||
|
|
||||||
struct ModuleArgs {
|
|
||||||
package_name: String,
|
|
||||||
file_name: String,
|
|
||||||
lib_name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<AttributeArgs> for ModuleArgs {
|
|
||||||
type Error = syn::Error;
|
|
||||||
|
|
||||||
fn try_from(args: AttributeArgs) -> Result<Self, Self::Error> {
|
|
||||||
let mut package_name = None;
|
|
||||||
let mut file_name = None;
|
|
||||||
let mut lib_name = None;
|
|
||||||
|
|
||||||
for arg in args {
|
|
||||||
match arg {
|
|
||||||
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
|
||||||
path,
|
|
||||||
eq_token: _,
|
|
||||||
lit: syn::Lit::Str(litstr),
|
|
||||||
})) => {
|
|
||||||
if path.is_ident("name") {
|
|
||||||
package_name = Some(litstr.value());
|
|
||||||
} else if path.is_ident("file") {
|
|
||||||
file_name = Some(litstr.value());
|
|
||||||
} else if path.is_ident("lib") {
|
|
||||||
lib_name = Some(litstr.value());
|
|
||||||
} else {
|
|
||||||
bail!(path => "unknown argument");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => bail!(Span::call_site(), "unexpected attribute argument"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let package_name = package_name
|
|
||||||
.ok_or_else(|| format_err!(Span::call_site(), "missing 'package' argument"))?;
|
|
||||||
|
|
||||||
let file_name =
|
|
||||||
file_name.unwrap_or_else(|| format!("{}.pm", package_name.replace("::", "/")));
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
package_name,
|
|
||||||
file_name,
|
|
||||||
lib_name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_module(attr: AttributeArgs, mut module: syn::ItemMod) -> Result<TokenStream, Error> {
|
fn handle_module(attr: AttributeArgs, mut module: syn::ItemMod) -> Result<TokenStream, Error> {
|
||||||
let args = ModuleArgs::try_from(attr)?;
|
let args = attribs::ModuleAttrs::try_from(attr)?;
|
||||||
|
|
||||||
let mut module_source = format!("package {};\n{}", args.package_name, MODULE_HEAD);
|
let mut module_source = format!("package {};\n{}", args.package_name, MODULE_HEAD);
|
||||||
|
|
||||||
@ -293,11 +264,29 @@ fn handle_module(attr: AttributeArgs, mut module: syn::ItemMod) -> Result<TokenS
|
|||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
match core::mem::replace(item, syn::Item::Verbatim(TokenStream::new())) {
|
match core::mem::replace(item, syn::Item::Verbatim(TokenStream::new())) {
|
||||||
syn::Item::Fn(mut func) => {
|
syn::Item::Fn(mut func) => {
|
||||||
let count = func.attrs.len();
|
let mut attribs = None;
|
||||||
func.attrs.retain(|attr| !attr.path.is_ident("export"));
|
for attr in std::mem::replace(&mut func.attrs, Default::default()) {
|
||||||
|
if attr.path.is_ident("export") {
|
||||||
|
if attribs.is_some() {
|
||||||
|
bail!(attr => "multiple 'export' attributes not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
let args: AttributeArgs =
|
||||||
|
Punctuated::<syn::NestedMeta, Token![,]>::parse_terminated
|
||||||
|
.parse2(attr.tokens)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
attribs = Some(attribs::FunctionAttrs::try_from(args)?);
|
||||||
|
} else {
|
||||||
|
// retain the attribute
|
||||||
|
func.attrs.push(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if we removed an #[export] macro this is an exported function:
|
// if we removed an #[export] macro this is an exported function:
|
||||||
if count != func.attrs.len() {
|
if let Some(attribs) = attribs {
|
||||||
let func = handle_function(func)?;
|
let func = handle_function(attribs, func)?;
|
||||||
*item = syn::Item::Verbatim(func.tokens);
|
*item = syn::Item::Verbatim(func.tokens);
|
||||||
module_source = format!(
|
module_source = format!(
|
||||||
"{} newXS('{}', '{}', 'src/FIXME.rs');\n",
|
"{} newXS('{}', '{}', 'src/FIXME.rs');\n",
|
||||||
|
Loading…
Reference in New Issue
Block a user