mirror of
https://github.com/topjohnwu/cxx.git
synced 2024-11-24 04:20:02 +00:00
115 lines
3.2 KiB
Rust
115 lines
3.2 KiB
Rust
// Functionality that is shared between the cxx::generate_bridge entry point and
|
|
// the cmd.
|
|
|
|
mod error;
|
|
pub(super) mod include;
|
|
pub(super) mod out;
|
|
mod write;
|
|
|
|
use self::error::format_err;
|
|
use self::out::OutFile;
|
|
use crate::syntax::{self, check, ident, Types};
|
|
use quote::quote;
|
|
use std::fs;
|
|
use std::io;
|
|
use std::path::Path;
|
|
use syn::parse::ParseStream;
|
|
use syn::{Attribute, File, Item, Token};
|
|
use thiserror::Error;
|
|
|
|
pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
|
|
|
|
#[derive(Error, Debug)]
|
|
pub(super) enum Error {
|
|
#[error("no #[cxx::bridge] module found")]
|
|
NoBridgeMod,
|
|
#[error("#[cxx::bridge] module must have inline contents")]
|
|
OutOfLineMod,
|
|
#[error(transparent)]
|
|
Io(#[from] io::Error),
|
|
#[error(transparent)]
|
|
Syn(#[from] syn::Error),
|
|
}
|
|
|
|
struct Input {
|
|
namespace: Vec<String>,
|
|
module: Vec<Item>,
|
|
}
|
|
|
|
pub(super) fn do_generate_bridge(path: &Path) -> OutFile {
|
|
let header = false;
|
|
generate(path, header)
|
|
}
|
|
|
|
pub(super) fn do_generate_header(path: &Path) -> OutFile {
|
|
let header = true;
|
|
generate(path, header)
|
|
}
|
|
|
|
fn generate(path: &Path, header: bool) -> OutFile {
|
|
let source = match fs::read_to_string(path) {
|
|
Ok(source) => source,
|
|
Err(err) => format_err(path, "", Error::Io(err)),
|
|
};
|
|
match (|| -> Result<_> {
|
|
let syntax = syn::parse_file(&source)?;
|
|
let bridge = find_bridge_mod(syntax)?;
|
|
let apis = syntax::parse_items(bridge.module)?;
|
|
let types = Types::collect(&apis)?;
|
|
check::typecheck(&apis, &types)?;
|
|
let out = write::gen(bridge.namespace, &apis, &types, header);
|
|
Ok(out)
|
|
})() {
|
|
Ok(out) => out,
|
|
Err(err) => format_err(path, &source, err),
|
|
}
|
|
}
|
|
|
|
fn find_bridge_mod(syntax: File) -> Result<Input> {
|
|
for item in syntax.items {
|
|
if let Item::Mod(item) = item {
|
|
for attr in &item.attrs {
|
|
let path = &attr.path;
|
|
if quote!(#path).to_string() == "cxx :: bridge" {
|
|
let module = match item.content {
|
|
Some(module) => module.1,
|
|
None => {
|
|
return Err(Error::Syn(syn::Error::new_spanned(
|
|
item,
|
|
Error::OutOfLineMod,
|
|
)));
|
|
}
|
|
};
|
|
return Ok(Input {
|
|
namespace: parse_args(attr)?,
|
|
module,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Err(Error::NoBridgeMod)
|
|
}
|
|
|
|
fn parse_args(attr: &Attribute) -> syn::Result<Vec<String>> {
|
|
if attr.tokens.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
attr.parse_args_with(|input: ParseStream| {
|
|
mod kw {
|
|
syn::custom_keyword!(namespace);
|
|
}
|
|
input.parse::<kw::namespace>()?;
|
|
input.parse::<Token![=]>()?;
|
|
let path = syn::Path::parse_mod_style(input)?;
|
|
input.parse::<Option<Token![,]>>()?;
|
|
path.segments
|
|
.into_iter()
|
|
.map(|seg| {
|
|
ident::check(&seg.ident)?;
|
|
Ok(seg.ident.to_string())
|
|
})
|
|
.collect()
|
|
})
|
|
}
|