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