Skip to main content

wai_bindgen_wasmtime_impl/
lib.rs

1use std::path::{Path, PathBuf};
2
3use proc_macro::TokenStream;
4use syn::parse::{Error, Parse, ParseStream, Result};
5use syn::punctuated::Punctuated;
6use syn::{token, Token};
7use wai_bindgen_gen_core::{wai_parser::Interface, Direction, Files, Generator};
8use wai_bindgen_gen_wasmtime::Async;
9
10/// Generate code to support consuming the given interfaces, importaing them
11/// from wasm modules.
12#[proc_macro]
13pub fn import(input: TokenStream) -> TokenStream {
14    run(input, Direction::Import)
15}
16
17/// Generate code to support implementing the given interfaces and exporting
18/// them to wasm modules.
19#[proc_macro]
20pub fn export(input: TokenStream) -> TokenStream {
21    run(input, Direction::Export)
22}
23
24fn run(input: TokenStream, dir: Direction) -> TokenStream {
25    let input = syn::parse_macro_input!(input as Opts);
26    let mut gen = input.opts.build();
27    let mut files = Files::default();
28    let (imports, exports) = match dir {
29        Direction::Import => (input.interfaces, vec![]),
30        Direction::Export => (vec![], input.interfaces),
31    };
32    gen.generate_all(&imports, &exports, &mut files);
33
34    let (_, contents) = files.iter().next().unwrap();
35
36    let contents = std::str::from_utf8(contents).unwrap();
37    let mut contents = contents.parse::<TokenStream>().unwrap();
38
39    // Include a dummy `include_str!` for any files we read so rustc knows that
40    // we depend on the contents of those files.
41    let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap();
42    for file in input.files.iter() {
43        contents.extend(
44            format!(
45                "const _: &str = include_str!(r#\"{}\"#);\n",
46                Path::new(&cwd).join(file).display()
47            )
48            .parse::<TokenStream>()
49            .unwrap(),
50        );
51    }
52
53    contents
54}
55
56struct Opts {
57    opts: wai_bindgen_gen_wasmtime::Opts,
58    interfaces: Vec<Interface>,
59    files: Vec<String>,
60}
61
62mod kw {
63    syn::custom_keyword!(src);
64    syn::custom_keyword!(paths);
65    syn::custom_keyword!(custom_error);
66}
67
68impl Parse for Opts {
69    fn parse(input: ParseStream<'_>) -> Result<Opts> {
70        let call_site = proc_macro2::Span::call_site();
71        let mut opts = wai_bindgen_gen_wasmtime::Opts::default();
72        let mut files = Vec::new();
73        opts.tracing = cfg!(feature = "tracing");
74
75        let interfaces = if input.peek(token::Brace) {
76            let content;
77            syn::braced!(content in input);
78            let mut interfaces = Vec::new();
79            let fields = Punctuated::<ConfigField, Token![,]>::parse_terminated(&content)?;
80            for field in fields.into_pairs() {
81                match field.into_value() {
82                    ConfigField::Interfaces(v) => interfaces = v,
83                    ConfigField::Async(v) => opts.async_ = v,
84                    ConfigField::CustomError(v) => opts.custom_error = v,
85                }
86            }
87            if interfaces.is_empty() {
88                return Err(Error::new(
89                    call_site,
90                    "must either specify `src` or `paths` keys",
91                ));
92            }
93            interfaces
94        } else {
95            while !input.is_empty() {
96                let s = input.parse::<syn::LitStr>()?;
97                files.push(s.value());
98            }
99            let mut interfaces = Vec::new();
100            let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
101            for path in files.iter() {
102                let path = manifest_dir.join(path);
103                let iface = Interface::parse_file(path).map_err(|e| Error::new(call_site, e))?;
104                interfaces.push(iface);
105            }
106            interfaces
107        };
108        Ok(Opts {
109            opts,
110            interfaces,
111            files,
112        })
113    }
114}
115
116enum ConfigField {
117    Interfaces(Vec<Interface>),
118    Async(wai_bindgen_gen_wasmtime::Async),
119    CustomError(bool),
120}
121
122impl Parse for ConfigField {
123    fn parse(input: ParseStream<'_>) -> Result<Self> {
124        let l = input.lookahead1();
125        if l.peek(kw::src) {
126            input.parse::<kw::src>()?;
127            let name;
128            syn::bracketed!(name in input);
129            let name = name.parse::<syn::LitStr>()?;
130            input.parse::<Token![:]>()?;
131            let s = input.parse::<syn::LitStr>()?;
132            let interface =
133                Interface::parse(&name.value(), &s.value()).map_err(|e| Error::new(s.span(), e))?;
134            Ok(ConfigField::Interfaces(vec![interface]))
135        } else if l.peek(kw::paths) {
136            input.parse::<kw::paths>()?;
137            input.parse::<Token![:]>()?;
138            let paths;
139            let bracket = syn::bracketed!(paths in input);
140            let paths = Punctuated::<syn::LitStr, Token![,]>::parse_terminated(&paths)?;
141            let values = paths.iter().map(|s| s.value()).collect::<Vec<_>>();
142            let mut interfaces = Vec::new();
143            let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
144            for value in &values {
145                let value = manifest_dir.join(value);
146                let interface =
147                    Interface::parse_file(value).map_err(|e| Error::new(bracket.span, e))?;
148                interfaces.push(interface);
149            }
150            Ok(ConfigField::Interfaces(interfaces))
151        } else if l.peek(token::Async) {
152            if !cfg!(feature = "async") {
153                return Err(
154                    input.error("async support not enabled in the `wai-bindgen-wasmtime` crate")
155                );
156            }
157            input.parse::<token::Async>()?;
158            input.parse::<Token![:]>()?;
159            let val = if input.parse::<Option<Token![*]>>()?.is_some() {
160                Async::All
161            } else {
162                let names;
163                syn::bracketed!(names in input);
164                let paths = Punctuated::<syn::LitStr, Token![,]>::parse_terminated(&names)?;
165                let values = paths.iter().map(|s| s.value()).collect();
166                Async::Only(values)
167            };
168            Ok(ConfigField::Async(val))
169        } else if l.peek(kw::custom_error) {
170            input.parse::<kw::custom_error>()?;
171            input.parse::<Token![:]>()?;
172            Ok(ConfigField::CustomError(
173                input.parse::<syn::LitBool>()?.value,
174            ))
175        } else {
176            Err(l.error())
177        }
178    }
179}