mlang_rs/lang/rustgen/
mapping.rs

1//! This module defines `trait`s and `fn`s that help map `mlang` types to `rust` types.
2
3use heck::{ToSnakeCase, ToUpperCamelCase};
4use proc_macro2::TokenStream;
5use quote::quote;
6
7use crate::lang::ir::{Comment, Enum, Field, Ident, Node, Type};
8
9/// A trait to help mapping [`Ident`] to rust type/field ident.
10pub trait CommentMapping {
11    /// Convert self to rust comment lines.
12    fn to_comment(&self) -> TokenStream;
13}
14
15impl CommentMapping for Comment {
16    fn to_comment(&self) -> TokenStream {
17        format!("/// {0}", self.1).parse().expect("to_comment")
18    }
19}
20
21/// A trait to help mapping [`Ident`] to rust type/field ident.
22pub trait IdentMapping {
23    /// Convert [`Ident`] to rust field name: xMinYMin => x_min_y_min.
24    fn to_field_name(&self) -> TokenStream;
25
26    /// Convert [`Ident`] to rust type(struct, enum, enum field) name: xMinYMin => XMinYMin.
27    fn to_type_name(&self) -> TokenStream;
28}
29
30impl IdentMapping for Ident {
31    fn to_field_name(&self) -> TokenStream {
32        match self.1.to_snake_case().as_str() {
33            "type" => "r#type".parse().expect("to_field_name"),
34            "in" => "r#in".parse().expect("to_field_name"),
35            "for" => "r#for".parse().expect("to_field_name"),
36            ident => ident.parse().expect("to_field_name"),
37        }
38    }
39
40    fn to_type_name(&self) -> TokenStream {
41        self.1.to_upper_camel_case().parse().expect("to_type_name")
42    }
43}
44
45/// A trait to help mapping [`Type`] to rust type.
46pub trait TypeMapping {
47    /// Convert [`Type`] to rust type definition.
48    fn to_definition(&self, ty_mod: &TokenStream) -> TokenStream;
49
50    /// Convert [`Type`] to rust where clause.
51    fn to_from_where_clause(
52        &self,
53        ty_mod: &TokenStream,
54        sexpr_mod: &TokenStream,
55        generic_ty: &TokenStream,
56    ) -> TokenStream;
57
58    /// Generate rust from clause for this type.
59    fn to_from_clause(&self, sexpr_mod: &TokenStream, param: &TokenStream) -> TokenStream;
60}
61
62impl TypeMapping for Type {
63    fn to_definition(&self, ty_mod: &TokenStream) -> TokenStream {
64        match self {
65            Type::Bool(_) => quote! {bool},
66            Type::String(_) => quote! {String},
67            Type::Byte(_) => quote! {i8},
68            Type::Ubyte(_) => quote! {u8},
69            Type::Short(_) => quote! {i16},
70            Type::Ushort(_) => quote! {u16},
71            Type::Int(_) => quote! {i32},
72            Type::Uint(_) => quote! {u32},
73            Type::Long(_) => quote! {i64},
74            Type::Ulong(_) => quote! {u64},
75            Type::Float(_) => quote! {f32},
76            Type::Double(_) => quote! {f64},
77            Type::Data(ident) => {
78                let ident = ident.to_type_name();
79
80                let ty_mod = ty_mod;
81
82                quote! { #ty_mod #ident }
83            }
84            Type::ListOf(component, _) => {
85                let component = component.to_definition(ty_mod);
86
87                quote! { Vec<#component> }
88            }
89            Type::ArrayOf(component, lit_num, _) => {
90                let component = component.to_definition(ty_mod);
91                let num = lit_num.0;
92
93                quote! { [#component;#num] }
94            }
95        }
96    }
97
98    fn to_from_where_clause(
99        &self,
100        ty_mod: &TokenStream,
101        sexpr_mod: &TokenStream,
102        generic_ty: &TokenStream,
103    ) -> TokenStream {
104        match self {
105            Type::Data(ident) => {
106                let ty = ident.to_type_name();
107                let ty_mod = ty_mod;
108                let generic_ty = generic_ty;
109                quote! {
110                   #ty_mod #ty : From<#generic_ty>
111                }
112            }
113            Type::ListOf(component, _) => {
114                let ty = component.to_definition(ty_mod);
115                let generic_ty = generic_ty;
116                quote! {
117                    #generic_ty: #sexpr_mod MapCollect<#ty>
118                }
119            }
120            Type::Float(_) => {
121                let generic_ty = generic_ty;
122                quote! { #sexpr_mod Number: From<#generic_ty> }
123            }
124            _ => {
125                let ty = self.to_definition(ty_mod);
126                let generic_ty = generic_ty;
127                quote! {
128                   #ty : From<#generic_ty>
129                }
130            }
131        }
132    }
133
134    fn to_from_clause(&self, sexpr_mod: &TokenStream, param: &TokenStream) -> TokenStream {
135        let param = param;
136
137        match self {
138            Type::ListOf(_, _) => {
139                quote! {
140                    #param.map_collect()
141                }
142            }
143            Type::Float(_) => {
144                quote! { #sexpr_mod Number::from(#param).0 }
145            }
146            _ => {
147                quote! {
148                    #param.into()
149                }
150            }
151        }
152    }
153}
154
155/// A trait to help mapping [`Field`] to rust field clause.
156pub trait FieldMapping: CommentMapping {
157    /// Generate rust from clause for this field: Some(value) or Variable::Constant(value)
158    fn to_from_clause(&self, sexpr_mod: &TokenStream, param: &TokenStream) -> TokenStream;
159
160    /// Generate rust struct field init clause: `a: 1usize` or `b: value.0`.
161    fn to_init_clause(&self, param: &TokenStream) -> TokenStream;
162
163    /// Convert field [`Type`] to rust filed type.
164    fn to_type_definition(&self, ty_mod: &TokenStream) -> TokenStream;
165
166    /// Generate rust field ident for [`Field`]
167    fn to_ident(&self) -> Option<TokenStream>;
168
169    /// Generate field definition clause for this field.
170    fn to_definition_clause(&self, vis: &TokenStream, ty: &TokenStream) -> TokenStream;
171}
172
173impl<'a> CommentMapping for Field<'a> {
174    fn to_comment(&self) -> TokenStream {
175        self.comments().iter().map(|c| c.to_comment()).collect()
176    }
177}
178
179impl<'a> FieldMapping for Field<'a> {
180    fn to_from_clause(&self, sexpr_mod: &TokenStream, param: &TokenStream) -> TokenStream {
181        let mut param = self.ty().to_from_clause(sexpr_mod, param);
182
183        if self.is_variable() {
184            param = quote! { mlang_rs::rt::opcode::Variable::Constant(#param) };
185        }
186
187        if self.is_option() {
188            param = quote! { Some(#param) };
189        }
190
191        param
192    }
193
194    fn to_init_clause(&self, param: &TokenStream) -> TokenStream {
195        if let Some(ident) = self.to_ident() {
196            quote! { #ident: #param }
197        } else {
198            quote! { #param }
199        }
200    }
201
202    fn to_type_definition(&self, ty_mod: &TokenStream) -> TokenStream {
203        let mut ty = self.ty().to_definition(ty_mod);
204
205        if self.is_variable() {
206            ty = quote! {
207                mlang_rs::rt::opcode::Variable<#ty>
208            };
209        }
210
211        if self.is_option() {
212            ty = quote! { Option<#ty> };
213        }
214
215        ty
216    }
217
218    fn to_definition_clause(&self, vis: &TokenStream, ty: &TokenStream) -> TokenStream {
219        let attrs = if self.is_option() {
220            quote! { #[serde(skip_serializing_if = "Option::is_none")] }
221        } else {
222            quote! {}
223        };
224
225        if let Some(ident) = self.to_ident() {
226            quote! { #attrs #vis #ident: #ty }
227        } else {
228            quote! { #attrs #vis #ty }
229        }
230    }
231
232    fn to_ident(&self) -> Option<TokenStream> {
233        self.ident().map(|ident| ident.to_field_name())
234    }
235}
236
237/// A trait to help mapping [`Node`] to rust struct.
238pub trait ComplexTypeMapping: CommentMapping {
239    /// Generate rust struct ident for [`Node`] .
240    fn to_ident(&self) -> TokenStream;
241
242    /// Generate `;` token if necessary
243    fn to_semi_token(&self) -> TokenStream;
244
245    /// Generate struct body clause.
246    fn to_struct_body(&self, fields: impl AsRef<[TokenStream]>) -> TokenStream;
247}
248
249impl CommentMapping for Node {
250    fn to_comment(&self) -> TokenStream {
251        self.comments.iter().map(|c| c.to_comment()).collect()
252    }
253}
254
255impl CommentMapping for Enum {
256    fn to_comment(&self) -> TokenStream {
257        self.comments.iter().map(|c| c.to_comment()).collect()
258    }
259}
260
261impl ComplexTypeMapping for Node {
262    fn to_ident(&self) -> TokenStream {
263        self.ident.to_type_name()
264    }
265
266    fn to_semi_token(&self) -> TokenStream {
267        if self.is_tuple() {
268            quote! {;}
269        } else {
270            quote! {}
271        }
272    }
273
274    fn to_struct_body(&self, fields: impl AsRef<[TokenStream]>) -> TokenStream {
275        let fields = fields.as_ref();
276
277        if fields.is_empty() {
278            return quote! {};
279        }
280
281        if self.is_tuple() {
282            quote! { (#(#fields),*) }
283        } else {
284            quote! { {#(#fields),*} }
285        }
286    }
287}
288
289impl ComplexTypeMapping for Enum {
290    fn to_ident(&self) -> TokenStream {
291        self.ident.to_type_name()
292    }
293
294    fn to_semi_token(&self) -> TokenStream {
295        quote! {}
296    }
297
298    fn to_struct_body(&self, fields: impl AsRef<[TokenStream]>) -> TokenStream {
299        let fields = fields.as_ref();
300
301        assert!(!fields.is_empty(), "enum fields is empty.");
302
303        if fields.is_empty() {
304            return quote! {};
305        }
306
307        quote! { {#(#fields),*} }
308    }
309}