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