1use std::collections::BTreeSet;
2
3use crate::util::*;
4use crate::{ClassDefs, FfiInterface, FfiType, TsFormat, TsModule};
5use dodo_bindgen_rust_ir::{Type, TypeNumeric};
6use proc_macro2::TokenStream;
7use syn::Ident;
8
9#[derive(Clone, Debug, PartialEq)]
14pub enum RustTypeNumeric {
15 U8,
16 U16,
17 U32,
18 I8,
19 I16,
20 I32,
21 F32,
22 F64,
23 U64,
24 I64,
25 Usize,
26 Isize,
27}
28
29#[derive(Clone, Debug, PartialEq)]
30pub enum RustType {
31 Void,
32 Numeric(RustTypeNumeric),
33 Boolean,
34 Char,
35 FnPtr(String), Ptr(Box<RustType>),
37 PtrMut(Box<RustType>),
38 Ref(Box<RustType>),
39 RefMut(Box<RustType>),
40 Box(Box<RustType>),
41 Str,
42 String,
43 Slice(Box<RustType>),
44 Vec(Box<RustType>),
45 Tuple(Vec<RustType>),
46 UserDefined(Ident),
47 Unsupported, }
49
50pub trait TypeExt {
55 fn unwrap(self, module: &mut TsModule) -> (FfiType, RustType);
56}
57
58impl TypeExt for Type {
59 fn unwrap(self, module: &mut TsModule) -> (FfiType, RustType) {
60 let TsModule {
61 type_defs,
62 user_defs,
63 ..
64 } = module;
65
66 fn match_str_or_slice(
67 elem: Box<Type>,
68 module: &mut TsModule,
69 fallback: &dyn Fn(Box<Type>, &mut TsModule) -> RustType,
70 ) -> RustType {
71 match *elem {
72 Type::Str => {
73 module.type_defs.insert(RustTypeList::Str);
74 RustType::Str
75 },
76 Type::Slice(elem) => {
77 module.type_defs.insert(RustTypeList::Slice);
78 let elem = Box::new(elem.unwrap(module).1);
79 RustType::Slice(elem)
80 },
81 _ => fallback(elem, module),
82 }
83 }
84
85 match self {
86 Type::Void => (FfiType::Void, RustType::Void),
87 Type::Numeric(type_numeric) => match type_numeric {
88 TypeNumeric::U8 => {
89 type_defs.insert(RustTypeList::U8);
90 (FfiType::U8, RustType::Numeric(RustTypeNumeric::U8))
91 },
92 TypeNumeric::U16 => {
93 type_defs.insert(RustTypeList::U16);
94 (FfiType::U16, RustType::Numeric(RustTypeNumeric::U16))
95 },
96 TypeNumeric::U32 => {
97 type_defs.insert(RustTypeList::U32);
98 (FfiType::U32, RustType::Numeric(RustTypeNumeric::U32))
99 },
100 TypeNumeric::I8 => {
101 type_defs.insert(RustTypeList::I8);
102 (FfiType::I8, RustType::Numeric(RustTypeNumeric::I8))
103 },
104 TypeNumeric::I16 => {
105 type_defs.insert(RustTypeList::I16);
106 (FfiType::I16, RustType::Numeric(RustTypeNumeric::I16))
107 },
108 TypeNumeric::I32 => {
109 type_defs.insert(RustTypeList::I32);
110 (FfiType::I32, RustType::Numeric(RustTypeNumeric::I32))
111 },
112 TypeNumeric::F32 => {
113 type_defs.insert(RustTypeList::F32);
114 (FfiType::F32, RustType::Numeric(RustTypeNumeric::F32))
115 },
116 TypeNumeric::F64 => {
117 type_defs.insert(RustTypeList::F64);
118 (FfiType::F64, RustType::Numeric(RustTypeNumeric::F64))
119 },
120 TypeNumeric::U64 => {
121 type_defs.insert(RustTypeList::U64);
122 (FfiType::U64, RustType::Numeric(RustTypeNumeric::U64))
123 },
124 TypeNumeric::I64 => {
125 type_defs.insert(RustTypeList::I64);
126 (FfiType::I64, RustType::Numeric(RustTypeNumeric::I64))
127 },
128 TypeNumeric::Usize => {
129 type_defs.insert(RustTypeList::Usize);
130 (FfiType::Usize, RustType::Numeric(RustTypeNumeric::Usize))
131 },
132 TypeNumeric::Isize => {
133 type_defs.insert(RustTypeList::Isize);
134 (FfiType::Isize, RustType::Numeric(RustTypeNumeric::Isize))
135 },
136 },
137 Type::Bool => (FfiType::Bool, RustType::Boolean),
138 Type::Char => {
139 type_defs.insert(RustTypeList::Char);
140 (FfiType::U32, RustType::Char)
141 },
142 Type::FnPtr(type_bare_fn) => {
143 type_defs.insert(RustTypeList::FnPtr);
144 (
145 FfiType::FnPointer,
146 RustType::FnPtr(type_bare_fn.to_token_stream().to_string()),
147 )
148 },
149 rest => (
150 FfiType::Pointer,
151 match rest {
152 Type::Ptr(elem) => match_str_or_slice(elem, module, &|elem, module| {
153 module.type_defs.insert(RustTypeList::Ptr);
154 let elem = Box::new(elem.unwrap(module).1);
155 RustType::Ptr(elem)
156 }),
157 Type::PtrMut(elem) => match_str_or_slice(elem, module, &|elem, module| {
158 module.type_defs.insert(RustTypeList::PtrMut);
159 let elem = Box::new(elem.unwrap(module).1);
160 RustType::PtrMut(elem)
161 }),
162 Type::Ref(elem) => match_str_or_slice(elem, module, &|elem, module| {
163 module.type_defs.insert(RustTypeList::Ref);
164 let elem = Box::new(elem.unwrap(module).1);
165 RustType::Ref(elem)
166 }),
167 Type::RefMut(elem) => match_str_or_slice(elem, module, &|elem, module| {
168 module.type_defs.insert(RustTypeList::RefMut);
169 let elem = Box::new(elem.unwrap(module).1);
170 RustType::RefMut(elem)
171 }),
172 Type::Box(elem) => match_str_or_slice(elem, module, &|elem, module| {
173 module.type_defs.insert(RustTypeList::Box);
174 RustType::Box(Box::new(elem.unwrap(module).1))
175 }),
176 Type::Str => {
177 type_defs.insert(RustTypeList::Str);
178 RustType::Str
179 },
180 Type::String => {
181 type_defs.insert(RustTypeList::String);
182 RustType::String
183 },
184 Type::Slice(elem) => {
185 type_defs.insert(RustTypeList::Slice);
186 RustType::Slice(Box::new(elem.unwrap(module).1))
187 },
188 Type::Array(_) => {
189 type_defs.insert(RustTypeList::Unsupported);
190 RustType::Unsupported
191 },
192 Type::Vec(elem) => {
193 type_defs.insert(RustTypeList::Vec);
194 RustType::Vec(Box::new(elem.unwrap(module).1))
195 },
196 Type::UserDefined(ident) => {
197 user_defs.insert(ident.clone());
198 RustType::UserDefined(ident)
199 },
200 Type::Tuple(elems) => {
201 type_defs.insert(RustTypeList::Tuple);
202 let mut tup_elems = Vec::new();
203 for elem in elems {
204 tup_elems.push(elem.unwrap(module).1);
205 }
206 RustType::Tuple(tup_elems)
207 },
208 Type::Unsupported(_) => {
209 type_defs.insert(RustTypeList::Unsupported);
210 RustType::Unsupported
211 },
212 _ => unreachable!(),
213 },
214 ),
215 }
216 }
217}
218
219#[cfg(test)]
224mod transform_tests {
225 use super::*;
226 use dodo_bindgen_rust_ir::{dbg_assert, parse_quote};
227
228 macro_rules! unwrap_type {
229 ($($tt:tt)*) => {{
230 let mut module = TsModule::default();
231 let actual = Type::unwrap(parse_quote!(Type, $($tt)*), &mut module);
232 actual.1
233 }};
234 }
235
236 #[test]
237 fn test_ts_type() {
238 dbg_assert!(unwrap_type!(()), RustType::Void);
239 }
240}
241
242#[rustfmt::skip]
247impl ToTokens for RustTypeNumeric {
248 fn to_tokens(&self, tokens: &mut TokenStream) {
249 tokens.extend(match self {
250 RustTypeNumeric::U8 => quote! { RustU8 },
251 RustTypeNumeric::U16 => quote! { RustU16 },
252 RustTypeNumeric::U32 => quote! { RustU32 },
253 RustTypeNumeric::I8 => quote! { RustI8 },
254 RustTypeNumeric::I16 => quote! { RustI16 },
255 RustTypeNumeric::I32 => quote! { RustI32 },
256 RustTypeNumeric::F32 => quote! { RustF32 },
257 RustTypeNumeric::F64 => quote! { RustF64 },
258 RustTypeNumeric::U64 => quote! { RustU64 },
259 RustTypeNumeric::I64 => quote! { RustI64 },
260 RustTypeNumeric::Usize => quote! { RustUsize },
261 RustTypeNumeric::Isize => quote! { RustIsize },
262 });
263 }
264}
265
266#[rustfmt::skip]
267impl ToTokens for RustType {
268 fn to_tokens(&self, tokens: &mut TokenStream) {
269 fn get_str_or_slice(elem: &Box<RustType>, fallback: TokenStream) -> TokenStream {
270 match &**elem {
271 RustType::Str => quote! { RustStr },
272 RustType::Slice(elem) => quote! { RustSlice<#elem> },
273 _ => fallback,
274 }
275 }
276
277 tokens.extend(match self {
278 RustType::Void => quote! { void },
279 RustType::Numeric(ts_type_numeric) => ts_type_numeric.to_token_stream(),
280 RustType::Boolean => quote! { boolean },
281 RustType::Char => quote! { RustChar },
282 RustType::FnPtr(sig) => quote! { RustFnPtr<#sig> },
283 RustType::Ptr(elem) =>
284 get_str_or_slice(elem, quote! { RustPtr<#elem> }),
285 RustType::PtrMut(elem) =>
286 get_str_or_slice(elem, quote! { RustPtrMut<#elem> }),
287 RustType::Ref(elem) =>
288 get_str_or_slice(elem, quote! { RustRef<#elem> }),
289 RustType::RefMut(elem) =>
290 get_str_or_slice(elem, quote! { RustRefMut<#elem> }),
291 RustType::Box(elem) =>
292 get_str_or_slice(elem, quote! { RustBox<#elem> }),
293 RustType::Str => quote! { RustStr },
294 RustType::String => quote! { RustString },
295 RustType::Slice(elem) => quote! { RustSlice<#elem> },
296 RustType::Vec(elem) => quote! { RustVec<#elem> },
297 RustType::Tuple(elem) => quote! { RustTuple<[#(#elem),*]> },
298 RustType::UserDefined(ident) => quote! { #ident },
299 RustType::Unsupported => quote! { RustUnsupportedType },
300 });
301 }
302}
303
304#[derive(Clone, Debug, Default)]
311pub struct UserDefinedDefs {
312 store: BTreeSet<Ident>,
313}
314
315impl UserDefinedDefs {
316 fn insert(&mut self, ident: Ident) {
317 self.store.insert(ident);
318 }
319
320 pub fn dedup(&mut self, class_defs: &ClassDefs) {
323 class_defs.store.keys().for_each(|class_name| {
324 self.store
325 .extract_if(.., |user_defined| *user_defined == *class_name)
326 .for_each(drop);
327 });
328 }
329}
330
331impl ToTokens for UserDefinedDefs {
332 fn to_tokens(&self, tokens: &mut TokenStream) {
333 tokens.extend(
334 self.store
335 .iter()
336 .map(|ident| {
337 quote! {
338
339 export class #ident extends RustPrototype<#ident> {}
340
341 }
342 })
343 .collect::<TokenStream>(),
344 );
345 }
346}
347
348#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
351enum RustTypeList {
352 U8,
353 U16,
354 U32,
355 I8,
356 I16,
357 I32,
358 F32,
359 F64,
360 U64,
361 Usize,
362 I64,
363 Isize,
364 Char,
365 FnPtr,
366 Ptr,
367 PtrMut,
368 Ref,
369 RefMut,
370 Box,
371 Str,
372 String,
373 Slice,
374 Vec,
375 Tuple,
376 Unsupported,
377 }
382
383impl ToTokens for RustTypeList {
384 fn to_tokens(&self, tokens: &mut TokenStream) {
385 tokens.extend(match self {
386 RustTypeList::U8 => quote! { RustU8 },
387 RustTypeList::U16 => quote! { RustU16 },
388 RustTypeList::U32 => quote! { RustU32 },
389 RustTypeList::I8 => quote! { RustI8 },
390 RustTypeList::I16 => quote! { RustI16 },
391 RustTypeList::I32 => quote! { RustI32 },
392 RustTypeList::F32 => quote! { RustF32 },
393 RustTypeList::F64 => quote! { RustF64 },
394 RustTypeList::U64 => quote! { RustU64 },
395 RustTypeList::Usize => quote! { RustUsize },
396 RustTypeList::I64 => quote! { RustI64 },
397 RustTypeList::Isize => quote! { RustIsize },
398 RustTypeList::Char => quote! { RustChar },
399 RustTypeList::FnPtr => quote! { RustFnPtr },
400 RustTypeList::Ptr => quote! { RustPtr },
401 RustTypeList::PtrMut => quote! { RustPtrMut },
402 RustTypeList::Ref => quote! { RustRef },
403 RustTypeList::RefMut => quote! { RustRefMut },
404 RustTypeList::Box => quote! { RustBox },
405 RustTypeList::Str => quote! { RustStr },
406 RustTypeList::String => quote! { RustString },
407 RustTypeList::Slice => quote! { RustSlice },
408 RustTypeList::Vec => quote! { RustVec },
409 RustTypeList::Tuple => quote! { RustTuple },
410 RustTypeList::Unsupported => quote! { RustUnsupportedType },
411 });
412 }
413}
414
415struct FileStr<'a> {
418 store: &'a str,
419}
420impl<'a> FileStr<'a> {
421 fn new(source: &'a str) -> Self {
422 Self { store: source }
423 }
424 fn slice_between(self, start_marker: &'a str, end_marker: &'a str) -> &'a str {
427 self.store
428 .split(start_marker)
429 .nth(1)
430 .expect("failed to match a start marker")
431 .split(end_marker)
432 .next()
433 .expect("start marker is missing a closing delimiter")
434 }
435 fn slice_out(self, start_marker: &'a str, end_marker: &'a str) -> Vec<&'a str> {
439 let mut out = Vec::new();
440 let match_count = self.store.matches(start_marker).count();
441 if match_count > 0 {
442 if match_count == self.store.matches(end_marker).count() {
443 let mut rest = self.store;
444 for _ in 0..match_count {
445 let (left, right) = rest.split_once(start_marker).unwrap();
446 out.push(left);
447 let (_, right) = right.split_once(end_marker).unwrap();
448 rest = right;
449 }
450 out.push(rest);
451 } else {
452 panic!("slice out marker is missing a closing pair");
453 }
454 } else {
455 out.push(self.store);
456 }
457 out
458 }
459}
460
461#[derive(Clone, Debug, Default)]
469pub struct RustTypeDefs {
470 store: BTreeSet<RustTypeList>,
471 pub extended: bool,
477}
478
479impl RustTypeDefs {
480 fn insert(&mut self, ty: RustTypeList) {
481 self.store.insert(ty);
482 }
483}
484
485impl RustTypeDefs {
486 pub fn print_inline(&self) -> (String, FfiInterface) {
488 const CONTENT_START: &str = "// <!-- deno-bindgen2-content-start -->\n";
489 const CONTENT_END: &str = "// <!-- deno-bindgen2-content-end -->\n";
490 const ALT_START: &str = "// <!-- deno-bindgen2-alt-type-start -->\n";
491 const ALT_END: &str = "// <!-- deno-bindgen2-alt-type-end -->\n";
492
493 let util_file = include_str!("../ts/util.ts");
494 let core_file = include_str!("../ts/core.ts");
495 let extended_file = include_str!("../ts/extended.ts");
496
497 let util = FileStr::new(&util_file).slice_between(CONTENT_START, CONTENT_END);
498 let core_types = FileStr::new(&core_file).slice_between(CONTENT_START, CONTENT_END);
499
500 let ffi_interface;
501
502 let mut module = String::new();
503 module.push_str(util);
504 module.push_str(core_types);
505 if self.extended {
506 let extended_types = FileStr::new(&extended_file).slice_between(ALT_START, ALT_END);
507 let extended_types = FileStr::new(extended_types).slice_out(
508 "// <!-- deno-bindgen2-ignore-start -->",
509 "// <!-- deno-bindgen2-ignore-end -->",
510 );
511 for slice in extended_types {
512 module.push_str(slice);
513 }
514
515 let _ffi_interface = <TokenStream as std::str::FromStr>::from_str(
516 FileStr::new(&extended_file).slice_between(
517 " // <!-- deno-bindgen2-ffi-symbols-start -->\n",
518 " // <!-- deno-bindgen2-ffi-symbols-end -->\n",
519 ),
520 )
521 .expect("failed to parse ffi symbol tokens");
522
523 ffi_interface = syn::parse2(_ffi_interface).expect("failed to parse ffi interface");
524 } else {
525 let core_alt_types = FileStr::new(&core_file).slice_between(ALT_START, ALT_END);
526 module.push_str(core_alt_types);
527 ffi_interface = FfiInterface::default();
528 }
529
530 (module, ffi_interface)
531 }
532
533 pub fn print_separate(
535 &self,
536 mut type_defs: String, type_defs_name: &str,
538 ) -> (String, String) {
539 let mut type_imports = Vec::new();
540 let mut class_imports = Vec::new();
541
542 for ty in &self.store {
543 match ty {
544 RustTypeList::Char
545 | RustTypeList::Box
546 | RustTypeList::Str
547 | RustTypeList::String
548 | RustTypeList::Slice
549 | RustTypeList::Vec
550 | RustTypeList::Tuple => class_imports.push(ty.to_token_stream()),
551 rest => type_imports.push(rest.to_token_stream()),
552 };
553 }
554
555 if type_imports.is_empty() && class_imports.is_empty() {
556 (String::new(), String::new())
557 } else {
558 class_imports.push(quote! { RustPrototype });
559
560 type_defs.push_str(
561 TsFormat::format(
562 quote! {
563
564 export type { #(#type_imports),* };
565 export { #(#class_imports),* };
566
567 }
568 .to_string(),
569 )
570 .as_str(),
571 );
572
573 let type_defs_name = format!("./{type_defs_name}");
574 let imports = TsFormat::format(
575 quote! {
576 import type { #(#type_imports),* } from #type_defs_name;
577 import { #(#class_imports),* } from #type_defs_name;
578 }
579 .to_string(),
580 );
581
582 (type_defs, imports)
583 }
584 }
585}
586
587#[cfg(test)]
592mod print_tests {
593 use super::*;
594 use crate::{FfiLib, TsFormat};
595
596 #[test]
597 fn test_include_str() {
598 let type_defs = RustTypeDefs::default();
599 let (module, _) = type_defs.print_inline();
600 println!("{module}");
601 }
602
603 #[test]
604 fn test_print() {}
605
606 #[test]
607 fn print_type_def() {
608 let mut type_defs = RustTypeDefs::default();
609 type_defs.extended = true;
610 let (module, ffi_interface) = type_defs.print_inline();
611 let mut ffi_lib = FfiLib::default();
612 ffi_lib.interface = ffi_interface;
613
614 println!(
615 "{}",
616 TsFormat::format(ffi_lib.to_token_stream().to_string())
617 );
618 println!("{module}");
619 }
620}