diff --git a/perlmod-macro/src/attribs.rs b/perlmod-macro/src/attribs.rs index 55ceaed..05ba4e3 100644 --- a/perlmod-macro/src/attribs.rs +++ b/perlmod-macro/src/attribs.rs @@ -6,7 +6,7 @@ use syn::AttributeArgs; pub struct ModuleAttrs { pub package_name: String, - pub file_name: String, + pub file_name: Option, pub lib_name: Option, } @@ -42,9 +42,6 @@ impl TryFrom for ModuleAttrs { 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, diff --git a/perlmod-macro/src/lib.rs b/perlmod-macro/src/lib.rs index 63bc616..cbaceb3 100644 --- a/perlmod-macro/src/lib.rs +++ b/perlmod-macro/src/lib.rs @@ -55,12 +55,12 @@ pub fn export(attr: TokenStream_1, item: TokenStream_1) -> TokenStream_1 { handle_error(item.clone(), export_impl(attr, item)).into() } -// /// Proc macro to create a perl package file for rust functions. -// #[proc_macro] -// pub fn make_package(item: TokenStream_1) -> TokenStream_1 { -// let item: TokenStream = item.into(); -// handle_error(item.clone(), make_package_impl(attr, item)).into() -// } +/// Proc macro to create a perl package file for rust functions. +#[proc_macro] +pub fn make_package(item: TokenStream_1) -> TokenStream_1 { + let item: TokenStream = item.into(); + handle_error(item.clone(), make_package_impl(item)).into() +} fn perlmod_impl(attr: AttributeArgs, item: TokenStream) -> Result { let item: syn::Item = syn::parse2(item)?; @@ -79,3 +79,9 @@ fn export_impl(attr: AttributeArgs, item: TokenStream) -> Result Result { + let pkg: package::Package = syn::parse2(item)?; + pkg.write()?; + Ok(TokenStream::new()) +} diff --git a/perlmod-macro/src/package.rs b/perlmod-macro/src/package.rs index 4f41f40..99aa495 100644 --- a/perlmod-macro/src/package.rs +++ b/perlmod-macro/src/package.rs @@ -4,7 +4,10 @@ use failure::Error; use proc_macro2::Ident; +use syn::parse::Parse; +use syn::punctuated::Punctuated; use syn::AttributeArgs; +use syn::Token; use crate::attribs::ModuleAttrs; @@ -104,7 +107,13 @@ impl Package { source = source.replace("{{LIB_NAME}}", LIB_NAME_DEFAULT); } - let path = std::path::Path::new(&self.attrs.file_name); + let file_name = self + .attrs + .file_name + .clone() + .unwrap_or_else(|| format!("{}.pm", self.attrs.package_name.replace("::", "/"))); + + let path = std::path::Path::new(&file_name); if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } @@ -113,3 +122,85 @@ impl Package { Ok(()) } } + +mod kw { + syn::custom_keyword!(package); + syn::custom_keyword!(lib); + syn::custom_keyword!(file); + syn::custom_keyword!(subs); +} + +impl Parse for Package { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut pkg = Package { + attrs: ModuleAttrs { + package_name: String::new(), + file_name: None, + lib_name: None, + }, + exported: Vec::new(), + }; + + // `package "Package::Name";` comes first + let _pkg: kw::package = input.parse()?; + let package: syn::LitStr = input.parse()?; + let _semicolon: Token![;] = input.parse()?; + pkg.attrs.package_name = package.value(); + + // `lib "lib_name";` optionally comes second + let lookahead = input.lookahead1(); + if lookahead.peek(kw::lib) { + let _lib: kw::lib = input.parse()?; + let lib: syn::LitStr = input.parse()?; + pkg.attrs.lib_name = Some(lib.value()); + let _semicolon: Token![;] = input.parse()?; + } + drop(lookahead); + + // `file "File/Name.pm";` optionally comes third + let lookahead = input.lookahead1(); + if lookahead.peek(kw::file) { + let _file: kw::file = input.parse()?; + let file: syn::LitStr = input.parse()?; + pkg.attrs.file_name = Some(file.value()); + let _semicolon: Token![;] = input.parse()?; + } + drop(lookahead); + + // `sub { ... }` must follow: + let _sub: kw::subs = input.parse()?; + let content; + let _brace_token: syn::token::Brace = syn::braced!(content in input); + let items: Punctuated = + content.parse_terminated(ExportItem::parse)?; + + for item in items { + match item { + ExportItem::Direct(name) => pkg.export_direct(name, "src/FIXME.rs".to_string()), + ExportItem::Named(name, as_name) => { + pkg.export_named(as_name, name, "src/FIXME.rs".to_string()); + } + } + } + + Ok(pkg) + } +} + +enum ExportItem { + Direct(Ident), + Named(Ident, Ident), +} + +impl Parse for ExportItem { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name: Ident = input.parse()?; + let lookahead = input.lookahead1(); + if lookahead.peek(syn::token::As) { + let _as: syn::token::As = input.parse()?; + Ok(ExportItem::Named(name, input.parse()?)) + } else { + Ok(ExportItem::Direct(name)) + } + } +} diff --git a/perlmod-test/src/lib.rs b/perlmod-test/src/lib.rs index 7e80253..1d323f6 100644 --- a/perlmod-test/src/lib.rs +++ b/perlmod-test/src/lib.rs @@ -1,13 +1,12 @@ -#[perlmod::package(name = "RSPM::Foo", lib = "perlmod_test")] -mod export { - use failure::{bail, Error}; +#[cfg(feature = "rust142")] +/// The fhe following requires rust 1.42 to work, as custom attributes on inline modules has only +/// been stabilized then. +mod pkg142; - #[export] - fn foo(a: u32, b: u32) -> Result { - if a == 42 { - bail!("dying on magic number"); - } +#[cfg(feature = "rustmuchlater")] +/// The following is what we ideally want to reach with future rust versions. It is technically +/// possible on nightly with #![feature(custom_inner_attributes)] +mod pkginline; - Ok(a + b) - } -} +/// This is possible on stable rust with some 1.3x already. +mod pkgstable; diff --git a/perlmod-test/src/pkg142.rs b/perlmod-test/src/pkg142.rs new file mode 100644 index 0000000..7e80253 --- /dev/null +++ b/perlmod-test/src/pkg142.rs @@ -0,0 +1,13 @@ +#[perlmod::package(name = "RSPM::Foo", lib = "perlmod_test")] +mod export { + use failure::{bail, Error}; + + #[export] + fn foo(a: u32, b: u32) -> Result { + if a == 42 { + bail!("dying on magic number"); + } + + Ok(a + b) + } +} diff --git a/perlmod-test/src/pkginline.rs b/perlmod-test/src/pkginline.rs new file mode 100644 index 0000000..75c0b43 --- /dev/null +++ b/perlmod-test/src/pkginline.rs @@ -0,0 +1,12 @@ +#![perlmod::package(name = "RSPM::Foo", lib = "perlmod_test")] + +use failure::{bail, Error}; + +#[export] +fn foo(a: u32, b: u32) -> Result { + if a == 42 { + bail!("dying on magic number"); + } + + Ok(a + b) +} diff --git a/perlmod-test/src/pkgstable.rs b/perlmod-test/src/pkgstable.rs new file mode 100644 index 0000000..f815eff --- /dev/null +++ b/perlmod-test/src/pkgstable.rs @@ -0,0 +1,26 @@ +use failure::{bail, Error}; + +#[perlmod::export] +fn foo(a: u32, b: u32) -> Result { + if a == 42 { + bail!("dying on magic number"); + } + + Ok(a + b) +} + +#[perlmod::export(name = "xs_a")] +fn func_b(a: u32) -> Result { + Ok(a * 2) +} + +perlmod::make_package! { + package "RSPM::Foo"; + + lib "perlmod_test"; + + subs { + foo, + xs_bar as bar, + } +} diff --git a/perlmod/src/lib.rs b/perlmod/src/lib.rs index 73faafc..a37db2f 100644 --- a/perlmod/src/lib.rs +++ b/perlmod/src/lib.rs @@ -26,4 +26,4 @@ pub mod value; pub use value::Value; #[cfg(feature = "exporter")] -pub use perlmod_macro::package; +pub use perlmod_macro::{export, make_package, package};