blob: df9f3eee47d3ed2799a8668340349cdf63a7d499 [file] [log] [blame]
David Tolnay7db73692019-10-20 14:51:12 -04001// Functionality that is shared between the cxx::generate_bridge entry point and
2// the cmd.
3
4mod error;
5pub(super) mod include;
6pub(super) mod out;
7mod write;
8
9use self::error::format_err;
10use self::out::OutFile;
11use crate::syntax::{self, check, ident, Types};
12use quote::quote;
13use std::fs;
14use std::io;
15use std::path::Path;
16use syn::parse::ParseStream;
17use syn::{Attribute, File, Item, Token};
18use thiserror::Error;
19
20pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
21
22#[derive(Error, Debug)]
23pub(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
34struct Input {
35 namespace: Vec<String>,
36 module: Vec<Item>,
37}
38
39pub(super) fn do_generate_bridge(path: &Path) -> OutFile {
40 let header = false;
41 generate(path, header)
42}
43
44pub(super) fn do_generate_header(path: &Path) -> OutFile {
45 let header = true;
46 generate(path, header)
47}
48
49fn 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
68fn 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
94fn 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}