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