1 | use crate::util::ShortHash; |
2 | use proc_macro2::{Ident, Span}; |
3 | use std::cell::{Cell, RefCell}; |
4 | use std::collections::HashMap; |
5 | use std::env; |
6 | use std::fs; |
7 | use std::path::PathBuf; |
8 | |
9 | use crate::ast; |
10 | use crate::Diagnostic; |
11 | |
12 | pub struct EncodeResult { |
13 | pub custom_section: Vec<u8>, |
14 | pub included_files: Vec<PathBuf>, |
15 | } |
16 | |
17 | pub fn encode(program: &ast::Program) -> Result<EncodeResult, Diagnostic> { |
18 | let mut e: Encoder = Encoder::new(); |
19 | let i: Interner = Interner::new(); |
20 | shared_program(program, &i)?.encode(&mut e); |
21 | let custom_section: Vec = e.finish(); |
22 | let included_files: Vec = iimpl Iterator |
23 | .files |
24 | .borrow() |
25 | .values() |
26 | .map(|p: &LocalFile| &p.path) |
27 | .cloned() |
28 | .collect(); |
29 | Ok(EncodeResult { |
30 | custom_section, |
31 | included_files, |
32 | }) |
33 | } |
34 | |
35 | struct Interner { |
36 | bump: bumpalo::Bump, |
37 | files: RefCell<HashMap<String, LocalFile>>, |
38 | root: PathBuf, |
39 | crate_name: String, |
40 | has_package_json: Cell<bool>, |
41 | } |
42 | |
43 | struct LocalFile { |
44 | path: PathBuf, |
45 | definition: Span, |
46 | new_identifier: String, |
47 | } |
48 | |
49 | impl Interner { |
50 | fn new() -> Interner { |
51 | let root = env::var_os("CARGO_MANIFEST_DIR" ) |
52 | .expect("should have CARGO_MANIFEST_DIR env var" ) |
53 | .into(); |
54 | let crate_name = env::var("CARGO_PKG_NAME" ).expect("should have CARGO_PKG_NAME env var" ); |
55 | Interner { |
56 | bump: bumpalo::Bump::new(), |
57 | files: RefCell::new(HashMap::new()), |
58 | root, |
59 | crate_name, |
60 | has_package_json: Cell::new(false), |
61 | } |
62 | } |
63 | |
64 | fn intern(&self, s: &Ident) -> &str { |
65 | self.intern_str(&s.to_string()) |
66 | } |
67 | |
68 | fn intern_str(&self, s: &str) -> &str { |
69 | // NB: eventually this could be used to intern `s` to only allocate one |
70 | // copy, but for now let's just "transmute" `s` to have the same |
71 | // lifetime as this struct itself (which is our main goal here) |
72 | self.bump.alloc_str(s) |
73 | } |
74 | |
75 | /// Given an import to a local module `id` this generates a unique module id |
76 | /// to assign to the contents of `id`. |
77 | /// |
78 | /// Note that repeated invocations of this function will be memoized, so the |
79 | /// same `id` will always return the same resulting unique `id`. |
80 | fn resolve_import_module(&self, id: &str, span: Span) -> Result<ImportModule, Diagnostic> { |
81 | let mut files = self.files.borrow_mut(); |
82 | if let Some(file) = files.get(id) { |
83 | return Ok(ImportModule::Named(self.intern_str(&file.new_identifier))); |
84 | } |
85 | self.check_for_package_json(); |
86 | let path = if let Some(id) = id.strip_prefix('/' ) { |
87 | self.root.join(id) |
88 | } else if id.starts_with("./" ) || id.starts_with("../" ) { |
89 | let msg = "relative module paths aren't supported yet" ; |
90 | return Err(Diagnostic::span_error(span, msg)); |
91 | } else { |
92 | return Ok(ImportModule::RawNamed(self.intern_str(id))); |
93 | }; |
94 | |
95 | // Generate a unique ID which is somewhat readable as well, so mix in |
96 | // the crate name, hash to make it unique, and then the original path. |
97 | let new_identifier = format!(" {}{}" , self.unique_crate_identifier(), id); |
98 | let file = LocalFile { |
99 | path, |
100 | definition: span, |
101 | new_identifier, |
102 | }; |
103 | files.insert(id.to_string(), file); |
104 | drop(files); |
105 | self.resolve_import_module(id, span) |
106 | } |
107 | |
108 | fn unique_crate_identifier(&self) -> String { |
109 | format!(" {}- {}" , self.crate_name, ShortHash(0)) |
110 | } |
111 | |
112 | fn check_for_package_json(&self) { |
113 | if self.has_package_json.get() { |
114 | return; |
115 | } |
116 | let path = self.root.join("package.json" ); |
117 | if path.exists() { |
118 | self.has_package_json.set(true); |
119 | } |
120 | } |
121 | } |
122 | |
123 | fn shared_program<'a>( |
124 | prog: &'a ast::Program, |
125 | intern: &'a Interner, |
126 | ) -> Result<Program<'a>, Diagnostic> { |
127 | Ok(Program { |
128 | exports: prog |
129 | .exports |
130 | .iter() |
131 | .map(|a| shared_export(a, intern)) |
132 | .collect::<Result<Vec<_>, _>>()?, |
133 | structs: prog |
134 | .structs |
135 | .iter() |
136 | .map(|a| shared_struct(a, intern)) |
137 | .collect(), |
138 | enums: prog.enums.iter().map(|a| shared_enum(a, intern)).collect(), |
139 | imports: prog |
140 | .imports |
141 | .iter() |
142 | .map(|a| shared_import(a, intern)) |
143 | .collect::<Result<Vec<_>, _>>()?, |
144 | typescript_custom_sections: prog |
145 | .typescript_custom_sections |
146 | .iter() |
147 | .map(|x| -> &'a str { x }) |
148 | .collect(), |
149 | linked_modules: prog |
150 | .linked_modules |
151 | .iter() |
152 | .enumerate() |
153 | .map(|(i, a)| shared_linked_module(&prog.link_function_name(i), a, intern)) |
154 | .collect::<Result<Vec<_>, _>>()?, |
155 | local_modules: intern |
156 | .files |
157 | .borrow() |
158 | .values() |
159 | .map(|file| { |
160 | fs::read_to_string(&file.path) |
161 | .map(|s| LocalModule { |
162 | identifier: intern.intern_str(&file.new_identifier), |
163 | contents: intern.intern_str(&s), |
164 | }) |
165 | .map_err(|e| { |
166 | let msg = format!("failed to read file ` {}`: {}" , file.path.display(), e); |
167 | Diagnostic::span_error(file.definition, msg) |
168 | }) |
169 | }) |
170 | .collect::<Result<Vec<_>, _>>()?, |
171 | inline_js: prog |
172 | .inline_js |
173 | .iter() |
174 | .map(|js| intern.intern_str(js)) |
175 | .collect(), |
176 | unique_crate_identifier: intern.intern_str(&intern.unique_crate_identifier()), |
177 | package_json: if intern.has_package_json.get() { |
178 | Some(intern.intern_str(intern.root.join("package.json" ).to_str().unwrap())) |
179 | } else { |
180 | None |
181 | }, |
182 | }) |
183 | } |
184 | |
185 | fn shared_export<'a>( |
186 | export: &'a ast::Export, |
187 | intern: &'a Interner, |
188 | ) -> Result<Export<'a>, Diagnostic> { |
189 | let consumed: bool = matches!(export.method_self, Some(ast::MethodSelf::ByValue)); |
190 | let method_kind: MethodKind<'_> = from_ast_method_kind(&export.function, intern, &export.method_kind)?; |
191 | Ok(Export { |
192 | class: export.js_class.as_deref(), |
193 | comments: export.comments.iter().map(|s: &String| &**s).collect(), |
194 | consumed, |
195 | function: shared_function(&export.function, intern), |
196 | method_kind, |
197 | start: export.start, |
198 | }) |
199 | } |
200 | |
201 | fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> { |
202 | let arg_names: Vec = funcimpl Iterator |
203 | .arguments |
204 | .iter() |
205 | .enumerate() |
206 | .map(|(idx: usize, arg: &PatType)| { |
207 | if let syn::Pat::Ident(x: &PatIdent) = &*arg.pat { |
208 | return x.ident.to_string(); |
209 | } |
210 | format!("arg {}" , idx) |
211 | }) |
212 | .collect::<Vec<_>>(); |
213 | Function { |
214 | arg_names, |
215 | asyncness: func.r#async, |
216 | name: &func.name, |
217 | generate_typescript: func.generate_typescript, |
218 | generate_jsdoc: func.generate_jsdoc, |
219 | variadic: func.variadic, |
220 | } |
221 | } |
222 | |
223 | fn shared_enum<'a>(e: &'a ast::Enum, intern: &'a Interner) -> Enum<'a> { |
224 | Enum { |
225 | name: &e.js_name, |
226 | variants: eimpl Iterator- >
|
227 | .variants |
228 | .iter() |
229 | .map(|v: &Variant| shared_variant(v, intern)) |
230 | .collect(), |
231 | comments: e.comments.iter().map(|s: &String| &**s).collect(), |
232 | generate_typescript: e.generate_typescript, |
233 | } |
234 | } |
235 | |
236 | fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<'a> { |
237 | EnumVariant { |
238 | name: intern.intern(&v.name), |
239 | value: v.value, |
240 | comments: v.comments.iter().map(|s: &String| &**s).collect(), |
241 | } |
242 | } |
243 | |
244 | fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<'a>, Diagnostic> { |
245 | Ok(Import { |
246 | module: iOption, …>> |
247 | .module |
248 | .as_ref() |
249 | .map(|m: &ImportModule| shared_module(m, intern)) |
250 | .transpose()?, |
251 | js_namespace: i.js_namespace.clone(), |
252 | kind: shared_import_kind(&i.kind, intern)?, |
253 | }) |
254 | } |
255 | |
256 | fn shared_linked_module<'a>( |
257 | name: &str, |
258 | i: &'a ast::ImportModule, |
259 | intern: &'a Interner, |
260 | ) -> Result<LinkedModule<'a>, Diagnostic> { |
261 | Ok(LinkedModule { |
262 | module: shared_module(m:i, intern)?, |
263 | link_function_name: intern.intern_str(name), |
264 | }) |
265 | } |
266 | |
267 | fn shared_module<'a>( |
268 | m: &'a ast::ImportModule, |
269 | intern: &'a Interner, |
270 | ) -> Result<ImportModule<'a>, Diagnostic> { |
271 | Ok(match m { |
272 | ast::ImportModule::Named(m: &String, span: &Span) => intern.resolve_import_module(id:m, *span)?, |
273 | ast::ImportModule::RawNamed(m: &String, _span: &Span) => ImportModule::RawNamed(intern.intern_str(m)), |
274 | ast::ImportModule::Inline(idx: &usize, _) => ImportModule::Inline(*idx as u32), |
275 | }) |
276 | } |
277 | |
278 | fn shared_import_kind<'a>( |
279 | i: &'a ast::ImportKind, |
280 | intern: &'a Interner, |
281 | ) -> Result<ImportKind<'a>, Diagnostic> { |
282 | Ok(match i { |
283 | ast::ImportKind::Function(f: &ImportFunction) => ImportKind::Function(shared_import_function(i:f, intern)?), |
284 | ast::ImportKind::Static(f: &ImportStatic) => ImportKind::Static(shared_import_static(i:f, intern)), |
285 | ast::ImportKind::Type(f: &ImportType) => ImportKind::Type(shared_import_type(i:f, intern)), |
286 | ast::ImportKind::Enum(f: &ImportEnum) => ImportKind::Enum(shared_import_enum(_i:f, intern)), |
287 | }) |
288 | } |
289 | |
290 | fn shared_import_function<'a>( |
291 | i: &'a ast::ImportFunction, |
292 | intern: &'a Interner, |
293 | ) -> Result<ImportFunction<'a>, Diagnostic> { |
294 | let method: Option> = match &i.kind { |
295 | ast::ImportFunctionKind::Method { class: &String, kind: &MethodKind, .. } => { |
296 | let kind: MethodKind<'_> = from_ast_method_kind(&i.function, intern, method_kind:kind)?; |
297 | Some(MethodData { class, kind }) |
298 | } |
299 | ast::ImportFunctionKind::Normal => None, |
300 | }; |
301 | |
302 | Ok(ImportFunction { |
303 | shim: intern.intern(&i.shim), |
304 | catch: i.catch, |
305 | method, |
306 | assert_no_shim: i.assert_no_shim, |
307 | structural: i.structural, |
308 | function: shared_function(&i.function, intern), |
309 | variadic: i.variadic, |
310 | }) |
311 | } |
312 | |
313 | fn shared_import_static<'a>(i: &'a ast::ImportStatic, intern: &'a Interner) -> ImportStatic<'a> { |
314 | ImportStatic { |
315 | name: &i.js_name, |
316 | shim: intern.intern(&i.shim), |
317 | } |
318 | } |
319 | |
320 | fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> ImportType<'a> { |
321 | ImportType { |
322 | name: &i.js_name, |
323 | instanceof_shim: &i.instanceof_shim, |
324 | vendor_prefixes: i.vendor_prefixes.iter().map(|x: &Ident| intern.intern(x)).collect(), |
325 | } |
326 | } |
327 | |
328 | fn shared_import_enum<'a>(_i: &'a ast::ImportEnum, _intern: &'a Interner) -> ImportEnum { |
329 | ImportEnum {} |
330 | } |
331 | |
332 | fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> { |
333 | Struct { |
334 | name: &s.js_name, |
335 | fields: simpl Iterator- >
|
336 | .fields |
337 | .iter() |
338 | .map(|s: &StructField| shared_struct_field(s, intern)) |
339 | .collect(), |
340 | comments: s.comments.iter().map(|s: &String| &**s).collect(), |
341 | is_inspectable: s.is_inspectable, |
342 | generate_typescript: s.generate_typescript, |
343 | } |
344 | } |
345 | |
346 | fn shared_struct_field<'a>(s: &'a ast::StructField, _intern: &'a Interner) -> StructField<'a> { |
347 | StructField { |
348 | name: &s.js_name, |
349 | readonly: s.readonly, |
350 | comments: s.comments.iter().map(|s: &String| &**s).collect(), |
351 | generate_typescript: s.generate_typescript, |
352 | generate_jsdoc: s.generate_jsdoc, |
353 | } |
354 | } |
355 | |
356 | trait Encode { |
357 | fn encode(&self, dst: &mut Encoder); |
358 | } |
359 | |
360 | struct Encoder { |
361 | dst: Vec<u8>, |
362 | } |
363 | |
364 | impl Encoder { |
365 | fn new() -> Encoder { |
366 | Encoder { |
367 | dst: vec![0, 0, 0, 0], |
368 | } |
369 | } |
370 | |
371 | fn finish(mut self) -> Vec<u8> { |
372 | let len: u32 = (self.dst.len() - 4) as u32; |
373 | self.dst[..4].copy_from_slice(&len.to_le_bytes()[..]); |
374 | self.dst |
375 | } |
376 | |
377 | fn byte(&mut self, byte: u8) { |
378 | self.dst.push(byte); |
379 | } |
380 | } |
381 | |
382 | impl Encode for bool { |
383 | fn encode(&self, dst: &mut Encoder) { |
384 | dst.byte(*self as u8); |
385 | } |
386 | } |
387 | |
388 | impl Encode for u32 { |
389 | fn encode(&self, dst: &mut Encoder) { |
390 | let mut val: u32 = *self; |
391 | while (val >> 7) != 0 { |
392 | dst.byte((val as u8) | 0x80); |
393 | val >>= 7; |
394 | } |
395 | assert_eq!(val >> 7, 0); |
396 | dst.byte(val as u8); |
397 | } |
398 | } |
399 | |
400 | impl Encode for usize { |
401 | fn encode(&self, dst: &mut Encoder) { |
402 | assert!(*self <= u32::max_value() as usize); |
403 | (*self as u32).encode(dst); |
404 | } |
405 | } |
406 | |
407 | impl<'a> Encode for &'a [u8] { |
408 | fn encode(&self, dst: &mut Encoder) { |
409 | self.len().encode(dst); |
410 | dst.dst.extend_from_slice(self); |
411 | } |
412 | } |
413 | |
414 | impl<'a> Encode for &'a str { |
415 | fn encode(&self, dst: &mut Encoder) { |
416 | self.as_bytes().encode(dst); |
417 | } |
418 | } |
419 | |
420 | impl Encode for String { |
421 | fn encode(&self, dst: &mut Encoder) { |
422 | self.as_bytes().encode(dst); |
423 | } |
424 | } |
425 | |
426 | impl<T: Encode> Encode for Vec<T> { |
427 | fn encode(&self, dst: &mut Encoder) { |
428 | self.len().encode(dst); |
429 | for item: &T in self { |
430 | item.encode(dst); |
431 | } |
432 | } |
433 | } |
434 | |
435 | impl<T: Encode> Encode for Option<T> { |
436 | fn encode(&self, dst: &mut Encoder) { |
437 | match self { |
438 | None => dst.byte(0), |
439 | Some(val: &T) => { |
440 | dst.byte(1); |
441 | val.encode(dst) |
442 | } |
443 | } |
444 | } |
445 | } |
446 | |
447 | macro_rules! encode_struct { |
448 | ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => { |
449 | struct $name $($lt)* { |
450 | $($field: $ty,)* |
451 | } |
452 | |
453 | impl $($lt)* Encode for $name $($lt)* { |
454 | fn encode(&self, _dst: &mut Encoder) { |
455 | $(self.$field.encode(_dst);)* |
456 | } |
457 | } |
458 | } |
459 | } |
460 | |
461 | macro_rules! encode_enum { |
462 | ($name:ident ($($lt:tt)*) $($fields:tt)*) => ( |
463 | enum $name $($lt)* { $($fields)* } |
464 | |
465 | impl$($lt)* Encode for $name $($lt)* { |
466 | fn encode(&self, dst: &mut Encoder) { |
467 | use self::$name::*; |
468 | encode_enum!(@arms self dst (0) () $($fields)*) |
469 | } |
470 | } |
471 | ); |
472 | |
473 | (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*)) => ( |
474 | encode_enum!(@expr match $me { $($arms)* }) |
475 | ); |
476 | |
477 | (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident, $($rest:tt)*) => ( |
478 | encode_enum!( |
479 | @arms |
480 | $me |
481 | $dst |
482 | ($cnt+1) |
483 | ($($arms)* $name => $dst.byte($cnt),) |
484 | $($rest)* |
485 | ) |
486 | ); |
487 | |
488 | (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident($t:ty), $($rest:tt)*) => ( |
489 | encode_enum!( |
490 | @arms |
491 | $me |
492 | $dst |
493 | ($cnt+1) |
494 | ($($arms)* $name(val) => { $dst.byte($cnt); val.encode($dst) }) |
495 | $($rest)* |
496 | ) |
497 | ); |
498 | |
499 | (@expr $e:expr) => ($e); |
500 | } |
501 | |
502 | macro_rules! encode_api { |
503 | () => (); |
504 | (struct $name:ident<'a> { $($fields:tt)* } $($rest:tt)*) => ( |
505 | encode_struct!($name (<'a>) $($fields)*); |
506 | encode_api!($($rest)*); |
507 | ); |
508 | (struct $name:ident { $($fields:tt)* } $($rest:tt)*) => ( |
509 | encode_struct!($name () $($fields)*); |
510 | encode_api!($($rest)*); |
511 | ); |
512 | (enum $name:ident<'a> { $($variants:tt)* } $($rest:tt)*) => ( |
513 | encode_enum!($name (<'a>) $($variants)*); |
514 | encode_api!($($rest)*); |
515 | ); |
516 | (enum $name:ident { $($variants:tt)* } $($rest:tt)*) => ( |
517 | encode_enum!($name () $($variants)*); |
518 | encode_api!($($rest)*); |
519 | ); |
520 | } |
521 | wasm_bindgen_shared::shared_api!(encode_api); |
522 | |
523 | fn from_ast_method_kind<'a>( |
524 | function: &'a ast::Function, |
525 | intern: &'a Interner, |
526 | method_kind: &'a ast::MethodKind, |
527 | ) -> Result<MethodKind<'a>, Diagnostic> { |
528 | Ok(match method_kind { |
529 | ast::MethodKind::Constructor => MethodKind::Constructor, |
530 | ast::MethodKind::Operation(ast::Operation { is_static, kind }) => { |
531 | let is_static = *is_static; |
532 | let kind = match kind { |
533 | ast::OperationKind::Getter(g) => { |
534 | let g = g.as_ref().map(|g| intern.intern(g)); |
535 | OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property())) |
536 | } |
537 | ast::OperationKind::Regular => OperationKind::Regular, |
538 | ast::OperationKind::Setter(s) => { |
539 | let s = s.as_ref().map(|s| intern.intern(s)); |
540 | OperationKind::Setter(match s { |
541 | Some(s) => s, |
542 | None => intern.intern_str(&function.infer_setter_property()?), |
543 | }) |
544 | } |
545 | ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter, |
546 | ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter, |
547 | ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter, |
548 | }; |
549 | MethodKind::Operation(Operation { is_static, kind }) |
550 | } |
551 | }) |
552 | } |
553 | |