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