| David Tolnay | 7db7369 | 2019-10-20 14:51:12 -0400 | [diff] [blame] | 1 | // Functionality that is shared between the cxx::generate_bridge entry point and |
| 2 | // the cmd. |
| 3 | |
| 4 | mod error; |
| 5 | pub(super) mod include; |
| 6 | pub(super) mod out; |
| 7 | mod write; |
| 8 | |
| 9 | use self::error::format_err; |
| 10 | use self::out::OutFile; |
| 11 | use crate::syntax::{self, check, ident, Types}; |
| 12 | use quote::quote; |
| 13 | use std::fs; |
| 14 | use std::io; |
| 15 | use std::path::Path; |
| 16 | use syn::parse::ParseStream; |
| 17 | use syn::{Attribute, File, Item, Token}; |
| 18 | use thiserror::Error; |
| 19 | |
| 20 | pub(super) type Result<T, E = Error> = std::result::Result<T, E>; |
| 21 | |
| 22 | #[derive(Error, Debug)] |
| 23 | pub(super) enum Error { |
| 24 | #[error("no #[cxx::bridge] module found")] |
| 25 | NoBridgeMod, |
| 26 | #[error("#[cxx::bridge] module must have inline contents")] |
| 27 | OutOfLineMod, |
| 28 | #[error(transparent)] |
| 29 | Io(#[from] io::Error), |
| 30 | #[error(transparent)] |
| 31 | Syn(#[from] syn::Error), |
| 32 | } |
| 33 | |
| 34 | struct Input { |
| 35 | namespace: Vec<String>, |
| 36 | module: Vec<Item>, |
| 37 | } |
| 38 | |
| 39 | pub(super) fn do_generate_bridge(path: &Path) -> OutFile { |
| 40 | let header = false; |
| 41 | generate(path, header) |
| 42 | } |
| 43 | |
| 44 | pub(super) fn do_generate_header(path: &Path) -> OutFile { |
| 45 | let header = true; |
| 46 | generate(path, header) |
| 47 | } |
| 48 | |
| 49 | fn generate(path: &Path, header: bool) -> OutFile { |
| 50 | let source = match fs::read_to_string(path) { |
| 51 | Ok(source) => source, |
| 52 | Err(err) => format_err(path, "", Error::Io(err)), |
| 53 | }; |
| 54 | match (|| -> Result<_> { |
| 55 | let syntax = syn::parse_file(&source)?; |
| 56 | let bridge = find_bridge_mod(syntax)?; |
| 57 | let apis = syntax::parse_items(bridge.module)?; |
| 58 | let types = Types::collect(&apis)?; |
| 59 | check::typecheck(&apis, &types)?; |
| 60 | let out = write::gen(bridge.namespace, &apis, &types, header); |
| 61 | Ok(out) |
| 62 | })() { |
| 63 | Ok(out) => out, |
| 64 | Err(err) => format_err(path, &source, err), |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | fn find_bridge_mod(syntax: File) -> Result<Input> { |
| 69 | for item in syntax.items { |
| 70 | if let Item::Mod(item) = item { |
| 71 | for attr in &item.attrs { |
| 72 | let path = &attr.path; |
| 73 | if quote!(#path).to_string() == "cxx :: bridge" { |
| 74 | let module = match item.content { |
| 75 | Some(module) => module.1, |
| 76 | None => { |
| 77 | return Err(Error::Syn(syn::Error::new_spanned( |
| 78 | item, |
| 79 | Error::OutOfLineMod, |
| 80 | ))); |
| 81 | } |
| 82 | }; |
| 83 | return Ok(Input { |
| 84 | namespace: parse_args(attr)?, |
| 85 | module, |
| 86 | }); |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | } |
| 91 | Err(Error::NoBridgeMod) |
| 92 | } |
| 93 | |
| 94 | fn parse_args(attr: &Attribute) -> syn::Result<Vec<String>> { |
| 95 | if attr.tokens.is_empty() { |
| 96 | return Ok(Vec::new()); |
| 97 | } |
| 98 | attr.parse_args_with(|input: ParseStream| { |
| 99 | mod kw { |
| 100 | syn::custom_keyword!(namespace); |
| 101 | } |
| 102 | input.parse::<kw::namespace>()?; |
| 103 | input.parse::<Token![=]>()?; |
| 104 | let path = syn::Path::parse_mod_style(input)?; |
| 105 | input.parse::<Option<Token![,]>>()?; |
| 106 | path.segments |
| 107 | .into_iter() |
| 108 | .map(|seg| { |
| 109 | ident::check(&seg.ident)?; |
| 110 | Ok(seg.ident.to_string()) |
| 111 | }) |
| 112 | .collect() |
| 113 | }) |
| 114 | } |