Skip to content

Commit

Permalink
Merge pull request #270 from dtolnay/parse
Browse files Browse the repository at this point in the history
Parse unsafe modules and unsafe extern blocks
  • Loading branch information
dtolnay authored Aug 29, 2020
2 parents 5e668bc + 0c0cfee commit 3c01ac2
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 78 deletions.
2 changes: 0 additions & 2 deletions gen/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
pub(super) enum Error {
NoBridgeMod,
OutOfLineMod,
Io(io::Error),
Syn(syn::Error),
}
Expand All @@ -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),
}
Expand Down
72 changes: 72 additions & 0 deletions gen/src/file.rs
Original file line number Diff line number Diff line change
@@ -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<Module>,
}

impl Parse for File {
fn parse(input: ParseStream) -> Result<Self> {
let mut modules = Vec::new();
input.call(Attribute::parse_inner)?;
parse(input, &mut modules)?;
Ok(File { modules })
}
}

fn parse(input: ParseStream, modules: &mut Vec<Module>) -> 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::<Visibility>()?;
ahead.parse::<Option<Token![unsafe]>>()?;
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::<Token![mod]>()?;
input.parse::<Ident>()?;
let semi: Option<Token![;]> = input.parse()?;
if semi.is_none() {
let content;
braced!(content in input);
parse(&content, modules)?;
}
}
}
Ok(())
}

fn parse_args(attr: &Attribute) -> Result<Namespace> {
if attr.tokens.is_empty() {
Ok(Namespace::none())
} else {
attr.parse_args()
}
}
47 changes: 0 additions & 47 deletions gen/src/find.rs

This file was deleted.

29 changes: 16 additions & 13 deletions gen/src/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// the cxxbridge CLI command.

mod error;
mod find;
mod file;
pub(super) mod include;
pub(super) mod out;
mod write;
Expand All @@ -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<Item>,
}

#[derive(Default)]
pub(super) struct Opt {
Expand All @@ -47,19 +41,28 @@ fn generate_from_path(path: &Path, opt: Opt, header: bool) -> Vec<u8> {
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<Vec<u8>> {
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);
Expand Down
21 changes: 11 additions & 10 deletions macro/src/expand.rs
Original file line number Diff line number Diff line change
@@ -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<TokenStream> {
pub fn bridge(mut ffi: Module) -> Result<TokenStream> {
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 {
Expand Down
8 changes: 5 additions & 3 deletions macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 { ... }`
///
Expand All @@ -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()
}
Expand Down
106 changes: 106 additions & 0 deletions syntax/file.rs
Original file line number Diff line number Diff line change
@@ -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<Attribute>,
pub vis: Visibility,
pub unsafety: Option<Token![unsafe]>,
pub mod_token: Token![mod],
pub ident: Ident,
pub brace_token: token::Brace,
pub content: Vec<Item>,
}

pub enum Item {
Struct(ItemStruct),
Enum(ItemEnum),
ForeignMod(ItemForeignMod),
Use(ItemUse),
Other(RustItem),
}

pub struct ItemForeignMod {
pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>,
pub abi: Abi,
pub brace_token: token::Brace,
pub items: Vec<ForeignItem>,
}

impl Parse for Module {
fn parse(input: ParseStream) -> Result<Self> {
let namespace = Namespace::none();
let mut attrs = input.call(Attribute::parse_outer)?;
let vis: Visibility = input.parse()?;
let unsafety: Option<Token![unsafe]> = input.parse()?;
let mod_token: Token![mod] = input.parse()?;
let ident: Ident = input.parse()?;

let semi: Option<Token![;]> = 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<Self> {
let attrs = input.call(Attribute::parse_outer)?;

let ahead = input.fork();
let unsafety = if ahead.parse::<Option<Token![unsafe]>>()?.is_some()
&& ahead.parse::<Option<Token![extern]>>()?.is_some()
&& ahead.parse::<Option<LitStr>>().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)),
}
}
}
1 change: 1 addition & 0 deletions syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod derive;
mod discriminant;
mod doc;
pub mod error;
pub mod file;
pub mod ident;
mod impls;
pub mod mangle;
Expand Down
Loading

0 comments on commit 3c01ac2

Please sign in to comment.