Skip to main content

mproto_codegen/codegen/js/
mod.rs

1use genco::prelude::*;
2
3use self::{js_enum::js_enum, js_struct::js_struct};
4use crate::{
5    ast::{PrimitiveType, QualifiedIdentifier, Type, TypeBody, TypeDef},
6    codegen::{CodegenCx, ResolvedType},
7};
8
9pub use package::{js_module_gen, js_package_gen};
10
11pub(crate) mod common;
12pub(crate) mod encoder_common;
13mod js_enum;
14mod js_struct;
15mod package;
16
17pub fn js_type_def(cx: &CodegenCx, type_def: &TypeDef) -> js::Tokens {
18    let cx = &cx.with_type_params(&type_def.params);
19
20    match &type_def.body {
21        TypeBody::Struct(struct_def) => js_struct(cx, &type_def.name, &type_def.params, struct_def),
22        TypeBody::Enum(enum_def) => js_enum(cx, &type_def.name, &type_def.params, enum_def),
23    }
24}
25
26pub fn js_type_tokens(cx: &CodegenCx, ty: &Type) -> js::Tokens {
27    match ty {
28        Type::Primitive(PrimitiveType::Void) => quote! { void },
29        Type::Primitive(PrimitiveType::U8) => quote! { number },
30        Type::Primitive(PrimitiveType::U16) => quote! { number },
31        Type::Primitive(PrimitiveType::U32) => quote! { number },
32        Type::Primitive(PrimitiveType::U64) => quote! { bigint },
33        Type::Primitive(PrimitiveType::U128) => quote! { bigint },
34        Type::Primitive(PrimitiveType::I8) => quote! { number },
35        Type::Primitive(PrimitiveType::I16) => quote! { number },
36        Type::Primitive(PrimitiveType::I32) => quote! { number },
37        Type::Primitive(PrimitiveType::I64) => quote! { bigint },
38        Type::Primitive(PrimitiveType::I128) => quote! { bigint },
39        Type::Primitive(PrimitiveType::F32) => quote! { number },
40        Type::Primitive(PrimitiveType::F64) => quote! { number },
41        Type::Primitive(PrimitiveType::Bool) => quote! { boolean },
42        Type::Primitive(PrimitiveType::String) => quote! { string },
43        Type::Primitive(PrimitiveType::Box(inner_ty)) => js_type_tokens(cx, inner_ty),
44        Type::Primitive(PrimitiveType::List(item_ty)) => quote! {
45            $(js_type_tokens(cx, item_ty))[]
46        },
47        Type::Primitive(PrimitiveType::Option(item_ty)) => quote! {
48            $(js_type_tokens(cx, item_ty)) | null
49        },
50        Type::Primitive(PrimitiveType::Result(ok_ty, err_ty)) => quote! {
51            $(js::import("@modrpc-org/mproto", "Result"))<$(js_type_tokens(cx, ok_ty)), $(js_type_tokens(cx, err_ty))>
52        },
53        Type::Defined { ident, args } => match cx.resolve_type(ident) {
54            Some(ResolvedType::Defined(_)) => {
55                let args = js_type_args(cx, args, js_type_tokens);
56                let import = cx.js_import_qualified(ident);
57                quote! { $(import)$(args) }
58            }
59            Some(ResolvedType::UnboundParam) => {
60                quote! { $(&ident.name) }
61            }
62            Some(ResolvedType::BoundParam { value, .. }) => js_type_tokens(cx, value),
63            None => {
64                panic!("js_type_tokens failed to resolve type: {:?}", ident);
65            }
66        },
67    }
68}
69
70pub fn js_type_lazy_tokens(cx: &CodegenCx, ty: &Type) -> js::Tokens {
71    match ty {
72        Type::Primitive(PrimitiveType::Box(inner_ty)) => js_type_lazy_tokens(cx, &inner_ty),
73        Type::Primitive(PrimitiveType::List(item_ty)) => {
74            let list_lazy = js::import("@modrpc-org/mproto", "ListLazy");
75            quote! { $list_lazy<$(js_type_lazy_tokens(cx, &item_ty))> }
76        }
77        Type::Primitive(PrimitiveType::Option(inner_ty)) => {
78            let option = js::import("@modrpc-org/mproto", "Option");
79            quote! { $option<$(js_type_lazy_tokens(cx, &inner_ty))> }
80        }
81        Type::Primitive(PrimitiveType::Result(ok_ty, err_ty)) => quote! {
82            $(js::import("@modrpc-org/mproto", "Result"))<$(js_type_lazy_tokens(cx, ok_ty)), $(js_type_lazy_tokens(cx, err_ty))>
83        },
84        Type::Primitive(_) => js_type_tokens(cx, ty),
85        Type::Defined { ident, args } => {
86            match cx.resolve_type(ident) {
87                Some(ResolvedType::Defined(type_def)) => {
88                    if type_def.body.is_enum() {
89                        // Lazy decoders aren't generated for enum types yet.
90                        let args = js_type_args(cx, args, js_type_tokens);
91                        let import = cx.js_import_qualified(&QualifiedIdentifier {
92                            name: format!("{}", ident.name),
93                            module: ident.module.clone(),
94                        });
95                        quote! { $(import)$(args) }
96                    } else {
97                        let args = js_type_args(cx, args, js_type_tokens);
98                        let import = cx.js_import_qualified(&QualifiedIdentifier {
99                            name: format!("{}Lazy", ident.name),
100                            module: ident.module.clone(),
101                        });
102                        quote! { $(import)$(args) }
103                    }
104                }
105                Some(ResolvedType::UnboundParam) => {
106                    quote! { $(&ident.name) }
107                }
108                Some(ResolvedType::BoundParam { value, .. }) => js_type_lazy_tokens(cx, value),
109                None => {
110                    panic!("js_type_lazy_tokens failed to resolve type: {:?}", ident);
111                }
112            }
113        }
114    }
115}
116
117pub fn js_type_args(
118    cx: &CodegenCx,
119    args: &[Type],
120    mut gen_tokens_fn: impl FnMut(&CodegenCx, &Type) -> js::Tokens,
121) -> js::Tokens {
122    if args.len() > 0 {
123        let arg_tokens = gen_tokens_fn(cx, &args[0]);
124        let mut args_items: js::Tokens = quote! { $arg_tokens };
125        for arg in &args[1..] {
126            let arg_tokens = gen_tokens_fn(cx, arg);
127            args_items = quote! { $args_items, $arg_tokens };
128        }
129
130        quote! { <$args_items> }
131    } else {
132        quote! {}
133    }
134}
135
136pub fn js_type_param_list(params: &[String]) -> js::Tokens {
137    if params.len() == 0 {
138        Tokens::new()
139    } else {
140        let mut tokens = quote! { <$(&params[0]) };
141
142        for param in &params[1..] {
143            tokens = quote! { $tokens, $param };
144        }
145
146        tokens = quote! { $tokens> };
147
148        tokens
149    }
150}
151
152pub fn js_encoder_type_args(
153    cx: &CodegenCx,
154    args: &[Type],
155    mut gen_tokens_fn: impl FnMut(&CodegenCx, &Type) -> js::Tokens,
156) -> js::Tokens {
157    if args.len() > 0 {
158        let arg_tokens = gen_tokens_fn(cx, &args[0]);
159        let mut args_items: js::Tokens = quote! { $arg_tokens };
160        for arg in &args[1..] {
161            let arg_tokens = gen_tokens_fn(cx, arg);
162            args_items = quote! { $args_items, $arg_tokens };
163        }
164
165        quote! { $args_items }
166    } else {
167        quote! {}
168    }
169}
170
171pub fn js_encoder_type_args_enclosed(
172    cx: &CodegenCx,
173    args: &[Type],
174    gen_tokens_fn: impl FnMut(&CodegenCx, &Type) -> js::Tokens,
175) -> js::Tokens {
176    if args.len() > 0 {
177        quote! { ($(js_encoder_type_args(cx, args, gen_tokens_fn))) }
178    } else {
179        quote! {}
180    }
181}
182
183pub fn js_type_encoder(cx: &CodegenCx, ty: &Type) -> js::Tokens {
184    match ty {
185        Type::Primitive(PrimitiveType::Void) => {
186            quote! { $(js::import("@modrpc-org/mproto", "ProtoVoid")) }
187        }
188        Type::Primitive(PrimitiveType::U8) => {
189            quote! { $(js::import("@modrpc-org/mproto", "ProtoUint8")) }
190        }
191        Type::Primitive(PrimitiveType::U16) => {
192            quote! { $(js::import("@modrpc-org/mproto", "ProtoUint16")) }
193        }
194        Type::Primitive(PrimitiveType::U32) => {
195            quote! { $(js::import("@modrpc-org/mproto", "ProtoUint32")) }
196        }
197        Type::Primitive(PrimitiveType::U64) => {
198            quote! { $(js::import("@modrpc-org/mproto", "ProtoUint64")) }
199        }
200        Type::Primitive(PrimitiveType::U128) => {
201            quote! { $(js::import("@modrpc-org/mproto", "ProtoUint128")) }
202        }
203        Type::Primitive(PrimitiveType::I8) => {
204            quote! { $(js::import("@modrpc-org/mproto", "ProtoInt8")) }
205        }
206        Type::Primitive(PrimitiveType::I16) => {
207            quote! { $(js::import("@modrpc-org/mproto", "ProtoInt16")) }
208        }
209        Type::Primitive(PrimitiveType::I32) => {
210            quote! { $(js::import("@modrpc-org/mproto", "ProtoInt32")) }
211        }
212        Type::Primitive(PrimitiveType::I64) => {
213            quote! { $(js::import("@modrpc-org/mproto", "ProtoInt64")) }
214        }
215        Type::Primitive(PrimitiveType::I128) => {
216            quote! { $(js::import("@modrpc-org/mproto", "ProtoInt128")) }
217        }
218        Type::Primitive(PrimitiveType::String) => {
219            quote! { $(js::import("@modrpc-org/mproto", "ProtoString")) }
220        }
221        Type::Primitive(PrimitiveType::F32) => {
222            quote! { $(js::import("@modrpc-org/mproto", "ProtoFloat32")) }
223        }
224        Type::Primitive(PrimitiveType::F64) => {
225            quote! { $(js::import("@modrpc-org/mproto", "ProtoFloat64")) }
226        }
227        Type::Primitive(PrimitiveType::Bool) => {
228            quote! { $(js::import("@modrpc-org/mproto", "ProtoBool")) }
229        }
230        Type::Primitive(PrimitiveType::Box(inner_ty)) => quote! {
231            $(js::import("@modrpc-org/mproto", "ProtoBox"))($(js_type_encoder(cx, inner_ty)))
232        },
233        Type::Primitive(PrimitiveType::List(item_ty)) => quote! {
234            $(js::import("@modrpc-org/mproto", "ProtoList"))($(js_type_encoder(cx, item_ty)))
235        },
236        Type::Primitive(PrimitiveType::Option(inner_ty)) => quote! {
237            $(js::import("@modrpc-org/mproto", "ProtoOption"))($(js_type_encoder(cx, inner_ty)))
238        },
239        Type::Primitive(PrimitiveType::Result(ok_ty, err_ty)) => quote! {
240            $(js::import("@modrpc-org/mproto", "ProtoResult"))($(js_type_encoder(cx, ok_ty)), $(js_type_encoder(cx, err_ty)))
241        },
242        Type::Defined { ident, args } => match cx.resolve_type(ident) {
243            Some(ResolvedType::Defined(_)) => {
244                let args = js_encoder_type_args_enclosed(cx, args, js_type_encoder);
245                let import = cx.js_import_qualified(&QualifiedIdentifier {
246                    name: format!("Proto{}", ident.name),
247                    module: ident.module.clone(),
248                });
249                quote! { $(import)$(args) }
250            }
251            Some(ResolvedType::UnboundParam) => {
252                quote! { this.$(&ident.name)Encoder }
253            }
254            Some(ResolvedType::BoundParam { value, .. }) => js_type_encoder(cx, value),
255            None => {
256                panic!("js_type_encoder failed to resolve type: {:?}", ident);
257            }
258        },
259    }
260}
261
262pub fn js_type_lazy_encoder(cx: &CodegenCx, ty: &Type) -> js::Tokens {
263    match ty {
264        Type::Primitive(PrimitiveType::Box(item_ty)) => quote! {
265            $(js::import("@modrpc-org/mproto", "ProtoBoxLazy"))($(js_type_lazy_encoder(cx, item_ty)))
266        },
267        Type::Primitive(PrimitiveType::List(item_ty)) => quote! {
268            $(js::import("@modrpc-org/mproto", "ProtoListLazy"))($(js_type_lazy_encoder(cx, item_ty)))
269        },
270        Type::Primitive(PrimitiveType::Option(inner_ty)) => quote! {
271            $(js::import("@modrpc-org/mproto", "ProtoOptionLazy"))($(js_type_lazy_encoder(cx, inner_ty)))
272        },
273        Type::Primitive(PrimitiveType::Result(ok_ty, err_ty)) => quote! {
274            $(js::import("@modrpc-org/mproto", "ProtoResultLazy"))($(js_type_lazy_encoder(cx, ok_ty)), $(js_type_lazy_encoder(cx, err_ty)))
275        },
276        Type::Primitive(_) => js_type_encoder(cx, ty),
277        Type::Defined { ident, args } => {
278            match cx.resolve_type(ident) {
279                Some(ResolvedType::Defined(type_def)) => {
280                    if type_def.body.is_enum() {
281                        // Lazy decoders aren't generated for enum types yet.
282                        let args = js_encoder_type_args_enclosed(cx, args, js_type_encoder);
283                        let import = cx.js_import_qualified(&QualifiedIdentifier {
284                            name: format!("Proto{}", ident.name),
285                            module: ident.module.clone(),
286                        });
287                        quote! { $(import)$(args) }
288                    } else {
289                        let args = js_encoder_type_args_enclosed(cx, args, js_type_encoder);
290                        let import = cx.js_import_qualified(&QualifiedIdentifier {
291                            name: format!("Proto{}Lazy", ident.name),
292                            module: ident.module.clone(),
293                        });
294                        quote! { $(import)$(args) }
295                    }
296                }
297                Some(ResolvedType::UnboundParam) => {
298                    quote! { this.$(&ident.name)Encoder }
299                }
300                Some(ResolvedType::BoundParam { value, .. }) => js_type_lazy_encoder(cx, value),
301                None => {
302                    panic!("js_type_lazy_encoder failed to resolve type: {:?}", ident);
303                }
304            }
305        }
306    }
307}