blob: 9a34705cf1e781eacc272330466b26d3664b8f97 [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
David Tolnay33d30292020-03-18 18:02:02 -070039#[derive(Default)]
40pub(super) struct Opt {
41 /// Any additional headers to #include
42 pub include: Vec<String>,
43}
44
45pub(super) fn do_generate_bridge(path: &Path, opt: Opt) -> OutFile {
David Tolnay7db73692019-10-20 14:51:12 -040046 let header = false;
David Tolnay33d30292020-03-18 18:02:02 -070047 generate(path, opt, header)
David Tolnay7db73692019-10-20 14:51:12 -040048}
49
David Tolnay33d30292020-03-18 18:02:02 -070050pub(super) fn do_generate_header(path: &Path, opt: Opt) -> OutFile {
David Tolnay7db73692019-10-20 14:51:12 -040051 let header = true;
David Tolnay33d30292020-03-18 18:02:02 -070052 generate(path, opt, header)
David Tolnay7db73692019-10-20 14:51:12 -040053}
54
David Tolnay33d30292020-03-18 18:02:02 -070055fn generate(path: &Path, opt: Opt, header: bool) -> OutFile {
David Tolnay7db73692019-10-20 14:51:12 -040056 let source = match fs::read_to_string(path) {
57 Ok(source) => source,
58 Err(err) => format_err(path, "", Error::Io(err)),
59 };
60 match (|| -> Result<_> {
61 let syntax = syn::parse_file(&source)?;
62 let bridge = find_bridge_mod(syntax)?;
63 let apis = syntax::parse_items(bridge.module)?;
64 let types = Types::collect(&apis)?;
65 check::typecheck(&apis, &types)?;
David Tolnay33d30292020-03-18 18:02:02 -070066 let out = write::gen(bridge.namespace, &apis, &types, opt, header);
David Tolnay7db73692019-10-20 14:51:12 -040067 Ok(out)
68 })() {
69 Ok(out) => out,
70 Err(err) => format_err(path, &source, err),
71 }
72}
73
74fn find_bridge_mod(syntax: File) -> Result<Input> {
75 for item in syntax.items {
76 if let Item::Mod(item) = item {
77 for attr in &item.attrs {
78 let path = &attr.path;
79 if quote!(#path).to_string() == "cxx :: bridge" {
80 let module = match item.content {
81 Some(module) => module.1,
82 None => {
83 return Err(Error::Syn(syn::Error::new_spanned(
84 item,
85 Error::OutOfLineMod,
86 )));
87 }
88 };
89 return Ok(Input {
90 namespace: parse_args(attr)?,
91 module,
92 });
93 }
94 }
95 }
96 }
97 Err(Error::NoBridgeMod)
98}
99
100fn 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}