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