1use crate::util::ShortHash;
2use proc_macro2::{Ident, Span};
3use std::cell::{Cell, RefCell};
4use std::collections::HashMap;
5use std::env;
6use std::fs;
7use std::path::PathBuf;
8
9use crate::ast;
10use crate::Diagnostic;
11
12pub struct EncodeResult {
13 pub custom_section: Vec<u8>,
14 pub included_files: Vec<PathBuf>,
15}
16
17pub 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
35struct 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
43struct LocalFile {
44 path: PathBuf,
45 definition: Span,
46 new_identifier: String,
47}
48
49impl 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
123fn 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
185fn 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
201fn 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
223fn 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
236fn 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
244fn 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
256fn 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
267fn 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
278fn 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
290fn 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
313fn 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
320fn 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
328fn shared_import_enum<'a>(_i: &'a ast::ImportEnum, _intern: &'a Interner) -> ImportEnum {
329 ImportEnum {}
330}
331
332fn 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
346fn 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
356trait Encode {
357 fn encode(&self, dst: &mut Encoder);
358}
359
360struct Encoder {
361 dst: Vec<u8>,
362}
363
364impl 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
382impl Encode for bool {
383 fn encode(&self, dst: &mut Encoder) {
384 dst.byte(*self as u8);
385 }
386}
387
388impl 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
400impl 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
407impl<'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
414impl<'a> Encode for &'a str {
415 fn encode(&self, dst: &mut Encoder) {
416 self.as_bytes().encode(dst);
417 }
418}
419
420impl Encode for String {
421 fn encode(&self, dst: &mut Encoder) {
422 self.as_bytes().encode(dst);
423 }
424}
425
426impl<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
435impl<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
447macro_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
461macro_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
502macro_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}
521wasm_bindgen_shared::shared_api!(encode_api);
522
523fn 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