wit_bindgen_guest_rust_macro/
lib.rs1use proc_macro2::{Span, TokenStream};
2use std::path::{Path, PathBuf};
3use syn::parse::{Error, Parse, ParseStream, Result};
4use syn::punctuated::Punctuated;
5use syn::{token, Token};
6use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};
7use wit_bindgen_gen_guest_rust::Opts;
8
9#[proc_macro]
10pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
11 syn::parse_macro_input!(input as Config)
12 .expand()
13 .unwrap_or_else(Error::into_compile_error)
14 .into()
15}
16
17struct Config {
18 opts: Opts,
19 resolve: Resolve,
20 world: WorldId,
21 files: Vec<PathBuf>,
22}
23
24enum Source {
25 Path(String),
26 Inline(String),
27}
28
29impl Parse for Config {
30 fn parse(input: ParseStream<'_>) -> Result<Self> {
31 let call_site = Span::call_site();
32 let mut opts = Opts::default();
33 let mut world = None;
34 let mut source = None;
35
36 let document = if input.peek(token::Brace) {
37 let content;
38 syn::braced!(content in input);
39 let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
40 let mut document = None;
41 for field in fields.into_pairs() {
42 match field.into_value() {
43 Opt::Path(s) => {
44 if source.is_some() {
45 return Err(Error::new(s.span(), "cannot specify second source"));
46 }
47 source = Some(Source::Path(s.value()));
48 }
49 Opt::World(s) => {
50 if document.is_some() {
51 return Err(Error::new(s.span(), "cannot specify second document"));
52 }
53 document = Some(parse_doc(&s.value(), &mut world));
54 }
55 Opt::Inline(s) => {
56 if source.is_some() {
57 return Err(Error::new(s.span(), "cannot specify second source"));
58 }
59 source = Some(Source::Inline(s.value()));
60 }
61 Opt::Unchecked => opts.unchecked = true,
62 Opt::NoStd => opts.no_std = true,
63 Opt::RawStrings => opts.raw_strings = true,
64 Opt::MacroExport => opts.macro_export = true,
65 Opt::MacroCallPrefix(prefix) => opts.macro_call_prefix = Some(prefix.value()),
66 Opt::ExportMacroName(name) => opts.export_macro_name = Some(name.value()),
67 Opt::Skip(list) => opts.skip.extend(list.iter().map(|i| i.value())),
68 }
69 }
70 match (document, &source) {
71 (Some(doc), _) => doc,
72 (None, Some(Source::Inline(_))) => "macro-input".to_string(),
73 _ => {
74 return Err(Error::new(
75 call_site,
76 "must specify a `world` to generate bindings for",
77 ))
78 }
79 }
80 } else {
81 let document = input.parse::<syn::LitStr>()?;
82 if input.parse::<Option<syn::token::In>>()?.is_some() {
83 source = Some(Source::Path(input.parse::<syn::LitStr>()?.value()));
84 }
85 parse_doc(&document.value(), &mut world)
86 };
87 let (resolve, pkg, files) =
88 parse_source(&source).map_err(|err| Error::new(call_site, format!("{err:?}")))?;
89 let doc = resolve.packages[pkg]
90 .documents
91 .get(&document)
92 .copied()
93 .ok_or_else(|| {
94 Error::new(call_site, format!("no document named `{document}` found"))
95 })?;
96
97 let world = match &world {
98 Some(name) => resolve.documents[doc]
99 .worlds
100 .get(name)
101 .copied()
102 .ok_or_else(|| Error::new(call_site, format!("no world named `{name}` found")))?,
103 None => resolve.documents[doc].default_world.ok_or_else(|| {
104 Error::new(call_site, format!("no default world found in `{document}`"))
105 })?,
106 };
107 Ok(Config {
108 opts,
109 resolve,
110 world,
111 files,
112 })
113 }
114}
115
116fn parse_source(source: &Option<Source>) -> anyhow::Result<(Resolve, PackageId, Vec<PathBuf>)> {
117 let mut resolve = Resolve::default();
118 let mut files = Vec::new();
119 let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
120 let mut parse = |path: &Path| -> anyhow::Result<_> {
121 if path.is_dir() {
122 let (pkg, sources) = resolve.push_dir(&path)?;
123 files = sources;
124 Ok(pkg)
125 } else {
126 let pkg = UnresolvedPackage::parse_file(path)?;
127 files.extend(pkg.source_files().map(|s| s.to_owned()));
128 resolve.push(pkg, &Default::default())
129 }
130 };
131 let pkg = match source {
132 Some(Source::Inline(s)) => resolve.push(
133 UnresolvedPackage::parse("macro-input".as_ref(), &s)?,
134 &Default::default(),
135 )?,
136 Some(Source::Path(s)) => parse(&root.join(&s))?,
137 None => parse(&root.join("wit"))?,
138 };
139
140 Ok((resolve, pkg, files))
141}
142
143fn parse_doc(s: &str, world: &mut Option<String>) -> String {
144 match s.find('.') {
145 Some(pos) => {
146 *world = Some(s[pos + 1..].to_string());
147 s[..pos].to_string()
148 }
149 None => s.to_string(),
150 }
151}
152
153impl Config {
154 fn expand(self) -> Result<TokenStream> {
155 let mut files = Default::default();
156 self.opts
157 .build()
158 .generate(&self.resolve, self.world, &mut files);
159 let (_, src) = files.iter().next().unwrap();
160 let src = std::str::from_utf8(src).unwrap();
161 let mut contents = src.parse::<TokenStream>().unwrap();
162
163 for file in self.files.iter() {
166 contents.extend(
167 format!("const _: &str = include_str!(r#\"{}\"#);\n", file.display())
168 .parse::<TokenStream>()
169 .unwrap(),
170 );
171 }
172
173 Ok(contents)
174 }
175}
176
177mod kw {
178 syn::custom_keyword!(unchecked);
179 syn::custom_keyword!(no_std);
180 syn::custom_keyword!(raw_strings);
181 syn::custom_keyword!(macro_export);
182 syn::custom_keyword!(macro_call_prefix);
183 syn::custom_keyword!(export_macro_name);
184 syn::custom_keyword!(skip);
185 syn::custom_keyword!(world);
186 syn::custom_keyword!(path);
187 syn::custom_keyword!(inline);
188}
189
190enum Opt {
191 World(syn::LitStr),
192 Path(syn::LitStr),
193 Inline(syn::LitStr),
194 Unchecked,
195 NoStd,
196 RawStrings,
197 MacroExport,
198 MacroCallPrefix(syn::LitStr),
199 ExportMacroName(syn::LitStr),
200 Skip(Vec<syn::LitStr>),
201}
202
203impl Parse for Opt {
204 fn parse(input: ParseStream<'_>) -> Result<Self> {
205 let l = input.lookahead1();
206 if l.peek(kw::path) {
207 input.parse::<kw::path>()?;
208 input.parse::<Token![:]>()?;
209 Ok(Opt::Path(input.parse()?))
210 } else if l.peek(kw::inline) {
211 input.parse::<kw::inline>()?;
212 input.parse::<Token![:]>()?;
213 Ok(Opt::Inline(input.parse()?))
214 } else if l.peek(kw::world) {
215 input.parse::<kw::world>()?;
216 input.parse::<Token![:]>()?;
217 Ok(Opt::World(input.parse()?))
218 } else if l.peek(kw::unchecked) {
219 input.parse::<kw::unchecked>()?;
220 Ok(Opt::Unchecked)
221 } else if l.peek(kw::no_std) {
222 input.parse::<kw::no_std>()?;
223 Ok(Opt::NoStd)
224 } else if l.peek(kw::raw_strings) {
225 input.parse::<kw::raw_strings>()?;
226 Ok(Opt::RawStrings)
227 } else if l.peek(kw::macro_export) {
228 input.parse::<kw::macro_export>()?;
229 Ok(Opt::MacroExport)
230 } else if l.peek(kw::macro_call_prefix) {
231 input.parse::<kw::macro_call_prefix>()?;
232 input.parse::<Token![:]>()?;
233 Ok(Opt::MacroCallPrefix(input.parse()?))
234 } else if l.peek(kw::export_macro_name) {
235 input.parse::<kw::export_macro_name>()?;
236 input.parse::<Token![:]>()?;
237 Ok(Opt::ExportMacroName(input.parse()?))
238 } else if l.peek(kw::skip) {
239 input.parse::<kw::skip>()?;
240 input.parse::<Token![:]>()?;
241 let contents;
242 syn::bracketed!(contents in input);
243 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
244 Ok(Opt::Skip(list.iter().cloned().collect()))
245 } else {
246 Err(l.error())
247 }
248 }
249}