diff --git a/gen/src/error.rs b/gen/src/error.rs index 51dbbe711..537351f08 100644 --- a/gen/src/error.rs +++ b/gen/src/error.rs @@ -16,7 +16,6 @@ pub(super) type Result = std::result::Result; #[derive(Debug)] pub(super) enum Error { NoBridgeMod, - OutOfLineMod, Io(io::Error), Syn(syn::Error), } @@ -25,7 +24,6 @@ impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::NoBridgeMod => write!(f, "no #[cxx::bridge] module found"), - Error::OutOfLineMod => write!(f, "#[cxx::bridge] module must have inline contents"), Error::Io(err) => err.fmt(f), Error::Syn(err) => err.fmt(f), } diff --git a/gen/src/file.rs b/gen/src/file.rs new file mode 100644 index 000000000..c69640711 --- /dev/null +++ b/gen/src/file.rs @@ -0,0 +1,72 @@ +use crate::syntax::file::Module; +use crate::syntax::namespace::Namespace; +use syn::parse::discouraged::Speculative; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::{braced, Attribute, Ident, Item, Token, Visibility}; + +pub struct File { + pub modules: Vec, +} + +impl Parse for File { + fn parse(input: ParseStream) -> Result { + let mut modules = Vec::new(); + input.call(Attribute::parse_inner)?; + parse(input, &mut modules)?; + Ok(File { modules }) + } +} + +fn parse(input: ParseStream, modules: &mut Vec) -> Result<()> { + while !input.is_empty() { + let mut cxx_bridge = false; + let mut namespace = Namespace::none(); + let mut attrs = input.call(Attribute::parse_outer)?; + for attr in &attrs { + let path = &attr.path.segments; + if path.len() == 2 && path[0].ident == "cxx" && path[1].ident == "bridge" { + cxx_bridge = true; + namespace = parse_args(attr)?; + break; + } + } + + let ahead = input.fork(); + ahead.parse::()?; + ahead.parse::>()?; + if !ahead.peek(Token![mod]) { + let item: Item = input.parse()?; + if cxx_bridge { + return Err(Error::new_spanned(item, "expected a module")); + } + continue; + } + + if cxx_bridge { + let mut module: Module = input.parse()?; + module.namespace = namespace; + attrs.extend(module.attrs); + module.attrs = attrs; + modules.push(module); + } else { + input.advance_to(&ahead); + input.parse::()?; + input.parse::()?; + let semi: Option = input.parse()?; + if semi.is_none() { + let content; + braced!(content in input); + parse(&content, modules)?; + } + } + } + Ok(()) +} + +fn parse_args(attr: &Attribute) -> Result { + if attr.tokens.is_empty() { + Ok(Namespace::none()) + } else { + attr.parse_args() + } +} diff --git a/gen/src/find.rs b/gen/src/find.rs deleted file mode 100644 index d897debce..000000000 --- a/gen/src/find.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::gen::{Error, Input, Result}; -use crate::syntax::namespace::Namespace; -use syn::{Attribute, File, Item}; - -pub(super) fn find_bridge_mod(syntax: File) -> Result { - match scan(syntax.items)? { - Some(input) => Ok(input), - None => Err(Error::NoBridgeMod), - } -} - -fn scan(items: Vec) -> Result> { - for item in items { - if let Item::Mod(item) = item { - for attr in &item.attrs { - let path = &attr.path.segments; - if path.len() == 2 && path[0].ident == "cxx" && path[1].ident == "bridge" { - let module = match item.content { - Some(module) => module.1, - None => { - return Err(Error::Syn(syn::Error::new_spanned( - item, - Error::OutOfLineMod, - ))); - } - }; - let namespace = parse_args(attr)?; - return Ok(Some(Input { namespace, module })); - } - } - if let Some(module) = item.content { - if let Some(input) = scan(module.1)? { - return Ok(Some(input)); - } - } - } - } - Ok(None) -} - -fn parse_args(attr: &Attribute) -> syn::Result { - if attr.tokens.is_empty() { - Ok(Namespace::none()) - } else { - attr.parse_args() - } -} diff --git a/gen/src/mod.rs b/gen/src/mod.rs index 710bca224..fa9e4073e 100644 --- a/gen/src/mod.rs +++ b/gen/src/mod.rs @@ -2,7 +2,7 @@ // the cxxbridge CLI command. mod error; -mod find; +mod file; pub(super) mod include; pub(super) mod out; mod write; @@ -11,17 +11,11 @@ mod write; mod tests; use self::error::{format_err, Error, Result}; -use crate::syntax::namespace::Namespace; +use self::file::File; use crate::syntax::report::Errors; use crate::syntax::{self, check, Types}; use std::fs; use std::path::Path; -use syn::Item; - -struct Input { - namespace: Namespace, - module: Vec, -} #[derive(Default)] pub(super) struct Opt { @@ -47,19 +41,28 @@ fn generate_from_path(path: &Path, opt: Opt, header: bool) -> Vec { Ok(source) => source, Err(err) => format_err(path, "", Error::Io(err)), }; - match generate(&source, opt, header) { + let mut source = source.as_str(); + if source.starts_with("#!") && !source.starts_with("#![") { + let shebang_end = source.find('\n').unwrap_or(source.len()); + source = &source[shebang_end..]; + } + match generate(source, opt, header) { Ok(out) => out, - Err(err) => format_err(path, &source, err), + Err(err) => format_err(path, source, err), } } fn generate(source: &str, opt: Opt, header: bool) -> Result> { proc_macro2::fallback::force(); let ref mut errors = Errors::new(); - let syntax = syn::parse_file(&source)?; - let bridge = find::find_bridge_mod(syntax)?; + let syntax: File = syn::parse_str(source)?; + let bridge = syntax + .modules + .into_iter() + .next() + .ok_or(Error::NoBridgeMod)?; let ref namespace = bridge.namespace; - let ref apis = syntax::parse_items(errors, bridge.module); + let ref apis = syntax::parse_items(errors, bridge.content); let ref types = Types::collect(errors, apis); errors.propagate()?; check::typecheck(errors, namespace, apis, types); diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 0869c2c58..aecec7528 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -1,32 +1,33 @@ use crate::syntax::atom::Atom::{self, *}; +use crate::syntax::file::Module; use crate::syntax::namespace::Namespace; use crate::syntax::report::Errors; use crate::syntax::symbol::Symbol; use crate::syntax::{ self, check, mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, TypeAlias, Types, }; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; -use syn::{parse_quote, Error, ItemMod, Result, Token}; +use std::mem; +use syn::{parse_quote, Result, Token}; -pub fn bridge(namespace: &Namespace, mut ffi: ItemMod) -> Result { +pub fn bridge(mut ffi: Module) -> Result { let ref mut errors = Errors::new(); - let content = ffi.content.take().ok_or(Error::new( - Span::call_site(), - "#[cxx::bridge] module must have inline contents", - ))?; - let ref apis = syntax::parse_items(errors, content.1); + let content = mem::take(&mut ffi.content); + let ref apis = syntax::parse_items(errors, content); let ref types = Types::collect(errors, apis); errors.propagate()?; + let namespace = &ffi.namespace; check::typecheck(errors, namespace, apis, types); errors.propagate()?; - Ok(expand(namespace, ffi, apis, types)) + Ok(expand(ffi, apis, types)) } -fn expand(namespace: &Namespace, ffi: ItemMod, apis: &[Api], types: &Types) -> TokenStream { +fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream { let mut expanded = TokenStream::new(); let mut hidden = TokenStream::new(); + let namespace = &ffi.namespace; for api in apis { if let Api::RustType(ety) = api { diff --git a/macro/src/lib.rs b/macro/src/lib.rs index bc8d19bbc..c2ad38b09 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -13,9 +13,10 @@ mod expand; mod syntax; mod type_id; +use crate::syntax::file::Module; use crate::syntax::namespace::Namespace; use proc_macro::TokenStream; -use syn::{parse_macro_input, ItemMod, LitStr}; +use syn::{parse_macro_input, LitStr}; /// `#[cxx::bridge] mod ffi { ... }` /// @@ -39,9 +40,10 @@ pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { let _ = syntax::error::ERRORS; let namespace = parse_macro_input!(args as Namespace); - let ffi = parse_macro_input!(input as ItemMod); + let mut ffi = parse_macro_input!(input as Module); + ffi.namespace = namespace; - expand::bridge(&namespace, ffi) + expand::bridge(ffi) .unwrap_or_else(|err| err.to_compile_error()) .into() } diff --git a/syntax/file.rs b/syntax/file.rs new file mode 100644 index 000000000..2141c1746 --- /dev/null +++ b/syntax/file.rs @@ -0,0 +1,106 @@ +use crate::syntax::namespace::Namespace; +use quote::quote; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::{ + braced, token, Abi, Attribute, ForeignItem, Ident, Item as RustItem, ItemEnum, ItemStruct, + ItemUse, LitStr, Token, Visibility, +}; + +pub struct Module { + pub namespace: Namespace, + pub attrs: Vec, + pub vis: Visibility, + pub unsafety: Option, + pub mod_token: Token![mod], + pub ident: Ident, + pub brace_token: token::Brace, + pub content: Vec, +} + +pub enum Item { + Struct(ItemStruct), + Enum(ItemEnum), + ForeignMod(ItemForeignMod), + Use(ItemUse), + Other(RustItem), +} + +pub struct ItemForeignMod { + pub attrs: Vec, + pub unsafety: Option, + pub abi: Abi, + pub brace_token: token::Brace, + pub items: Vec, +} + +impl Parse for Module { + fn parse(input: ParseStream) -> Result { + let namespace = Namespace::none(); + let mut attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let unsafety: Option = input.parse()?; + let mod_token: Token![mod] = input.parse()?; + let ident: Ident = input.parse()?; + + let semi: Option = input.parse()?; + if let Some(semi) = semi { + let span = quote!(#vis #mod_token #semi); + return Err(Error::new_spanned( + span, + "#[cxx::bridge] module must have inline contents", + ))?; + } + + let content; + let brace_token = braced!(content in input); + attrs.extend(content.call(Attribute::parse_inner)?); + + let mut items = Vec::new(); + while !content.is_empty() { + items.push(content.parse()?); + } + + Ok(Module { + namespace, + attrs, + vis, + unsafety, + mod_token, + ident, + brace_token, + content: items, + }) + } +} + +impl Parse for Item { + fn parse(input: ParseStream) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + + let ahead = input.fork(); + let unsafety = if ahead.parse::>()?.is_some() + && ahead.parse::>()?.is_some() + && ahead.parse::>().is_ok() + && ahead.peek(token::Brace) + { + Some(input.parse()?) + } else { + None + }; + + let item = input.parse()?; + match item { + RustItem::Struct(item) => Ok(Item::Struct(ItemStruct { attrs, ..item })), + RustItem::Enum(item) => Ok(Item::Enum(ItemEnum { attrs, ..item })), + RustItem::ForeignMod(item) => Ok(Item::ForeignMod(ItemForeignMod { + attrs: item.attrs, + unsafety, + abi: item.abi, + brace_token: item.brace_token, + items: item.items, + })), + RustItem::Use(item) => Ok(Item::Use(ItemUse { attrs, ..item })), + other => Ok(Item::Other(other)), + } + } +} diff --git a/syntax/mod.rs b/syntax/mod.rs index cd6f2359e..ae37802de 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -7,6 +7,7 @@ mod derive; mod discriminant; mod doc; pub mod error; +pub mod file; pub mod ident; mod impls; pub mod mangle; diff --git a/syntax/parse.rs b/syntax/parse.rs index e195081f4..c23ee8f93 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -1,4 +1,5 @@ use crate::syntax::discriminant::DiscriminantSet; +use crate::syntax::file::{Item, ItemForeignMod}; use crate::syntax::report::Errors; use crate::syntax::Atom::*; use crate::syntax::{ @@ -11,8 +12,8 @@ use syn::parse::{ParseStream, Parser}; use syn::punctuated::Punctuated; use syn::{ Abi, Attribute, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType, - GenericArgument, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, LitStr, Pat, PathArguments, - Result, ReturnType, Token, Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice, + GenericArgument, Ident, ItemEnum, ItemStruct, LitStr, Pat, PathArguments, Result, ReturnType, + Token, Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice, }; pub mod kw { @@ -33,7 +34,7 @@ pub fn parse_items(cx: &mut Errors, items: Vec) -> Vec { }, Item::ForeignMod(foreign_mod) => parse_foreign_mod(cx, foreign_mod, &mut apis), Item::Use(item) => cx.error(item, error::USE_NOT_ALLOWED), - _ => cx.error(item, "unsupported item"), + Item::Other(item) => cx.error(item, "unsupported item"), } } apis