1#![feature(string_remove_matches)]
2
3use proc_macro::TokenStream;
61use proc_macro2::{Delimiter, Ident, Span, TokenStream as TokenStream2, TokenTree};
62use quote::quote;
63use std::env;
64use std::fs::{create_dir_all, File, OpenOptions};
65use std::io::prelude::*;
66use std::process::Command;
67use toml;
68use toml::Value;
69
70fn token_tree_to_toml(tree: TokenTree, prev: &Option<TokenTree>) -> String {
71 let mut buf = String::new();
72 let newline_group = if let Some(TokenTree::Punct(_)) = prev {
73 ""
74 } else {
75 "\n"
76 };
77 buf = match tree {
78 TokenTree::Group(group) => match group.delimiter() {
79 Delimiter::Brace => format![
80 "{}{}{{{}}}",
81 newline_group,
82 buf,
83 token_stream_to_toml(group.stream())
84 ],
85 Delimiter::Bracket => format![
86 "{}{}[{}]",
87 newline_group,
88 buf,
89 token_stream_to_toml(group.stream())
90 ],
91 Delimiter::Parenthesis => {
92 format![
93 "{}{}({})",
94 newline_group,
95 buf,
96 token_stream_to_toml(group.stream())
97 ]
98 }
99 Delimiter::None => format![
100 "{}{}{}",
101 newline_group,
102 buf,
103 token_stream_to_toml(group.stream())
104 ],
105 },
106 TokenTree::Ident(ident) => {
107 if let Some(TokenTree::Group(_) | TokenTree::Literal(_)) = prev {
108 format!["\n{}{}", buf, ident.to_string()]
109 } else {
110 format!["{}{}", buf, ident.to_string()]
111 }
112 }
113 TokenTree::Literal(literal) => {
114 if let Some(TokenTree::Group(_)) = prev {
115 format!["\n{}{}", buf, literal.to_string()]
116 } else {
117 format!["{}{}", buf, literal.to_string()]
118 }
119 }
120 TokenTree::Punct(punct) => {
121 if let Some(TokenTree::Group(_)) = prev {
122 format!["\n{}{}", buf, punct.to_string()]
123 } else {
124 format!["{}{}", buf, punct.to_string()]
125 }
126 }
127 };
128 buf
129}
130
131fn token_stream_to_toml(tokens: TokenStream2) -> String {
132 let mut buf = String::new();
133 let mut prev = None;
134
135 for token in tokens.into_iter() {
136 buf = format!["{}{}", buf, token_tree_to_toml(token.clone(), &prev)];
137 prev = Some(token);
138 }
139
140 buf
141}
142
143#[proc_macro_attribute]
144pub fn wasmir(attr: TokenStream, input: TokenStream) -> TokenStream {
145 let project_root = std::path::PathBuf::from(
146 std::env::var("CARGO_MANIFEST_DIR")
147 .expect("couldn't read CARGO_MANIFEST_DIR environment variable"),
148 );
149 let wasmir_dir = std::path::PathBuf::from(project_root.clone()).join(".wasmir");
150 create_dir_all(wasmir_dir.clone()).expect("couldn't create WASMIR temp directory");
151
152 let attr = TokenStream2::from(attr);
153 let dependencies = token_stream_to_toml(attr);
154 println!["{}", dependencies];
155
156 let input = TokenStream2::from(input);
157 let mut module_name = String::new();
158 let mut module_stream: TokenStream2 = TokenStream2::new();
159
160 for item in input.clone().into_iter() {
161 match item {
162 TokenTree::Ident(ident) => match ident.to_string().as_str() {
163 "pub" => {
164 continue;
165 }
166 "mod" => {
167 continue;
168 }
169 name => {
170 module_name = name.to_string();
171 }
172 },
173 TokenTree::Group(group) => {
174 module_stream = group.stream();
175 break;
176 }
177 _ => {
178 continue;
179 }
180 }
181 }
182
183 let module_text = module_stream.to_string();
184 let module_root = wasmir_dir.join(module_name.clone());
185 env::set_current_dir(wasmir_dir.clone()).expect("could not set current directory");
187 match Command::new("cargo")
188 .arg("new")
189 .arg("--lib")
190 .arg(module_name.clone())
191 .output()
192 {
193 Ok(o) => {
194 println!["{}", String::from_utf8(o.stderr).unwrap()];
195 }
196 Err(_) => {}
197 };
198
199 let mut file = File::create(module_root.join("src").join("lib.rs"))
201 .expect("could not open lib.rs for editing");
202 let buf: Vec<u8> = module_text.as_bytes().iter().map(|b| *b).collect();
203 file.write_all(&buf).expect("could not write to lib.rs");
204
205 let mut buf = String::new();
207 let mut file = OpenOptions::new()
208 .write(true)
209 .read(true)
210 .open(module_root.join("Cargo.toml"))
211 .expect("no Cargo.toml in module root");
212
213 file.read_to_string(&mut buf)
214 .expect("failed to read from Cargo.toml");
215
216 let mut cargo_toml: toml::Value =
217 toml::from_str(&mut buf.as_str()).expect("failed to parse toml for module");
218
219 let cdylib: Value = Value::Array(vec![toml::Value::String("cdylib".to_string())]);
220
221 match cargo_toml.get_mut("lib") {
222 Some(Value::Table(lib)) => {
223 lib.insert(
224 "crate-type".to_string(),
225 Value::Array(vec![Value::String("cdylib".to_string())]),
226 );
227 }
228 _ => {
229 if let Some(table) = cargo_toml.as_table_mut() {
230 let mut map = toml::map::Map::new();
231 map.insert("crate-type".to_string(), cdylib);
232 let map = Value::Table(map);
233 table.insert("lib".to_string(), map);
234 }
235 }
236 }
237
238 let wasm_bindgen_dep: Value = Value::String("*".to_string());
239
240 match cargo_toml.get_mut("dependencies") {
241 Some(Value::Table(lib)) => {
242 lib.insert("wasm-bindgen".to_string(), Value::String("*".to_string()));
243 }
244 _ => {
245 if let Some(table) = cargo_toml.as_table_mut() {
246 let mut map = toml::map::Map::new();
247 map.insert("wasm-bindgen".to_string(), wasm_bindgen_dep);
248 let map = Value::Table(map);
249 table.insert("dependencies".to_string(), map);
250 }
251 }
252 }
253
254 let mut dependencies_toml: Value =
255 toml::from_str(&dependencies).expect("failed to parse dependencies toml");
256 match dependencies_toml.get_mut("dependencies") {
257 Some(Value::Table(deps)) => {
258 if let Some(Value::Table(lib_deps)) = cargo_toml.get_mut("dependencies") {
259 lib_deps.extend(deps.iter().map(|(k, v)| (k.clone(), v.clone())));
260 }
261 }
262 _ => {}
263 }
264
265 let mut file =
266 File::create(module_root.join("Cargo.toml")).expect("failed to write toml/create file");
267 file.write(&format!["{}", cargo_toml].bytes().collect::<Vec<u8>>())
268 .expect("failed to write to Cargo.toml");
269
270 env::set_current_dir(module_root.clone()).expect("could not set current directory");
272 match Command::new("wasm-pack")
273 .arg("build")
274 .arg("--target")
275 .arg("web")
276 .output()
277 {
278 Ok(o) => {
279 println!["{}", String::from_utf8(o.stderr).unwrap()];
280 }
281 Err(e) => {
282 panic!["could not build: {}", e];
283 }
284 }
285
286 let mut file = match File::open(
287 module_root
288 .join("pkg")
289 .join(format!["{}_bg.wasm", module_name.clone()]),
290 ) {
291 Ok(file) => file,
292 Err(e) => panic!["could not open binary: {}", e],
293 };
294
295 let mut binary = vec![];
296
297 file.read_to_end(&mut binary)
298 .expect("could not read-in binary");
299
300 let binary_len = binary.len();
301
302 let mut file = match File::open(
303 module_root
304 .join("pkg")
305 .join(format!["{}.js", module_name.clone()]),
306 ) {
307 Ok(file) => file,
308 Err(e) => panic!["could not open js: {}", e],
309 };
310
311 let mut js = String::new();
312
313 file.read_to_string(&mut js).expect("could not read-in js");
314
315 let module_name = Ident::new(module_name.as_str(), Span::call_site());
316
317 quote![
318 mod #module_name {
319 #input
320 pub const wasm: [u8; #binary_len] = [#(#binary),*];
321 pub const loader: &str = #js;
322 }]
323 .into()
324}