zngur_generator/
lib.rs

1use std::collections::hash_map::Entry;
2
3use cpp::CppExportedFnDefinition;
4use cpp::CppExportedImplDefinition;
5use cpp::CppFile;
6use cpp::CppFnDefinition;
7use cpp::CppFnSig;
8use cpp::CppMethod;
9use cpp::CppPath;
10use cpp::CppTraitDefinition;
11use cpp::CppType;
12use cpp::CppTypeDefinition;
13use cpp::cpp_handle_keyword;
14use itertools::Itertools;
15use rust::IntoCpp;
16
17pub mod cpp;
18mod rust;
19
20pub use rust::RustFile;
21pub use zngur_parser::ParsedZngFile;
22
23pub use zngur_def::*;
24
25pub struct ZngurGenerator(ZngurSpec);
26
27impl ZngurGenerator {
28    pub fn build_from_zng(zng: ZngurSpec) -> Self {
29        ZngurGenerator(zng)
30    }
31
32    pub fn render(self) -> (String, String, Option<String>) {
33        let mut zng = self.0;
34
35        // Unit type is a bit special, and almost everyone needs it, so we add it ourself.
36        zng.types.push(ZngurType {
37            ty: RustType::UNIT,
38            layout: LayoutPolicy::ZERO_SIZED_TYPE,
39            wellknown_traits: vec![ZngurWellknownTrait::Copy],
40            methods: vec![],
41            constructors: vec![],
42            fields: vec![],
43            cpp_value: None,
44            cpp_ref: None,
45        });
46        let mut cpp_file = CppFile::default();
47        cpp_file.additional_includes = zng.additional_includes.0;
48        let mut rust_file = RustFile::default();
49        cpp_file.trait_defs = zng
50            .traits
51            .iter()
52            .map(|(key, value)| (key.clone(), rust_file.add_builder_for_dyn_trait(value)))
53            .collect();
54        if zng.convert_panic_to_exception.0 {
55            rust_file.enable_panic_to_exception();
56            cpp_file.panic_to_exception = true;
57        }
58        for ty_def in zng.types {
59            let ty = &ty_def.ty;
60            let is_copy = ty_def.wellknown_traits.contains(&ZngurWellknownTrait::Copy);
61            match ty_def.layout {
62                LayoutPolicy::StackAllocated { size, align } => {
63                    rust_file.add_static_size_assert(&ty, size);
64                    rust_file.add_static_align_assert(&ty, align);
65                }
66                LayoutPolicy::HeapAllocated => (),
67                LayoutPolicy::OnlyByRef => (),
68            }
69            if is_copy {
70                rust_file.add_static_is_copy_assert(&ty);
71            }
72            let mut cpp_methods = vec![];
73            let mut constructors = vec![];
74            let mut fields = vec![];
75            let mut wellknown_traits = vec![];
76            for constructor in ty_def.constructors {
77                match constructor.name {
78                    Some(name) => {
79                        let rust_link_names = rust_file
80                            .add_constructor(&format!("{}::{}", ty, name), &constructor.inputs);
81                        cpp_methods.push(CppMethod {
82                            name: cpp_handle_keyword(&name).to_owned(),
83                            kind: ZngurMethodReceiver::Static,
84                            sig: CppFnSig {
85                                rust_link_name: rust_link_names.constructor,
86                                inputs: constructor.inputs.iter().map(|x| x.1.into_cpp()).collect(),
87                                output: ty.into_cpp(),
88                            },
89                        });
90                        cpp_methods.push(CppMethod {
91                            name: format!("matches_{}", name),
92                            kind: ZngurMethodReceiver::Ref(Mutability::Not),
93                            sig: CppFnSig {
94                                rust_link_name: rust_link_names.match_check,
95                                inputs: vec![ty.into_cpp().into_ref()],
96                                output: CppType::from("uint8_t"),
97                            },
98                        });
99                    }
100                    None => {
101                        let rust_link_name = rust_file
102                            .add_constructor(&format!("{}", ty), &constructor.inputs)
103                            .constructor;
104                        constructors.push(CppFnSig {
105                            rust_link_name,
106                            inputs: constructor.inputs.iter().map(|x| x.1.into_cpp()).collect(),
107                            output: ty.into_cpp(),
108                        });
109                    }
110                }
111            }
112            for field in ty_def.fields {
113                rust_file.add_field_assertions(&field, &ty_def.ty);
114                fields.push(field);
115            }
116            if let RustType::Tuple(fields) = &ty_def.ty {
117                if !fields.is_empty() {
118                    let rust_link_name = rust_file.add_tuple_constructor(&fields);
119                    constructors.push(CppFnSig {
120                        rust_link_name,
121                        inputs: fields.iter().map(|x| x.into_cpp()).collect(),
122                        output: ty.into_cpp(),
123                    });
124                }
125            }
126            let is_unsized = ty_def
127                .wellknown_traits
128                .contains(&ZngurWellknownTrait::Unsized);
129            for wellknown_trait in ty_def.wellknown_traits {
130                let data = rust_file.add_wellknown_trait(&ty, wellknown_trait, is_unsized);
131                wellknown_traits.push(data);
132            }
133            for method_details in ty_def.methods {
134                let ZngurMethodDetails {
135                    data: method,
136                    use_path,
137                    deref,
138                } = method_details;
139                let (rusty_inputs, inputs) = real_inputs_of_method(&method, &ty);
140                let rust_link_name = rust_file.add_function(
141                    &format!(
142                        "<{}>::{}::<{}>",
143                        deref.as_ref().unwrap_or(&ty),
144                        method.name,
145                        method.generics.iter().join(", "),
146                    ),
147                    &rusty_inputs,
148                    &method.output,
149                    use_path,
150                    deref.is_some(),
151                );
152                cpp_methods.push(CppMethod {
153                    name: cpp_handle_keyword(&method.name).to_owned(),
154                    kind: method.receiver,
155                    sig: CppFnSig {
156                        rust_link_name,
157                        inputs,
158                        output: method.output.into_cpp(),
159                    },
160                });
161            }
162            cpp_file.type_defs.push(CppTypeDefinition {
163                ty: ty.into_cpp(),
164                layout: rust_file.add_layout_policy_shim(&ty, ty_def.layout),
165                constructors,
166                fields,
167                methods: cpp_methods,
168                wellknown_traits,
169                cpp_value: ty_def.cpp_value.map(|mut cpp_value| {
170                    cpp_value.0 = rust_file.add_cpp_value_bridge(&ty, &cpp_value.0);
171                    cpp_value
172                }),
173                cpp_ref: ty_def.cpp_ref,
174                from_trait: if let RustType::Boxed(b) = &ty {
175                    if let RustType::Dyn(tr, _) = b.as_ref() {
176                        if let RustTrait::Fn {
177                            name,
178                            inputs,
179                            output,
180                        } = tr
181                        {
182                            if let Entry::Vacant(e) = cpp_file.trait_defs.entry(tr.clone()) {
183                                let rust_link_name =
184                                    rust_file.add_builder_for_dyn_fn(name, inputs, output);
185                                e.insert(CppTraitDefinition::Fn {
186                                    sig: CppFnSig {
187                                        rust_link_name,
188                                        inputs: inputs.iter().map(|x| x.into_cpp()).collect(),
189                                        output: output.into_cpp(),
190                                    },
191                                });
192                            }
193                        }
194                        Some(tr.clone())
195                    } else {
196                        None
197                    }
198                } else {
199                    None
200                },
201                from_trait_ref: if let RustType::Dyn(tr, _) = &ty {
202                    Some(tr.clone())
203                } else {
204                    None
205                },
206            });
207        }
208        for func in zng.funcs {
209            let rust_link_name = rust_file.add_function(
210                &func.path.to_string(),
211                &func.inputs,
212                &func.output,
213                None,
214                false,
215            );
216            cpp_file.fn_defs.push(CppFnDefinition {
217                name: CppPath::from_rust_path(&func.path.path),
218                sig: CppFnSig {
219                    rust_link_name,
220                    inputs: func.inputs.into_iter().map(|x| x.into_cpp()).collect(),
221                    output: func.output.into_cpp(),
222                },
223            });
224        }
225        for func in zng.extern_cpp_funcs {
226            let rust_link_name =
227                rust_file.add_extern_cpp_function(&func.name, &func.inputs, &func.output);
228            cpp_file.exported_fn_defs.push(CppExportedFnDefinition {
229                name: func.name.clone(),
230                sig: CppFnSig {
231                    rust_link_name,
232                    inputs: func.inputs.into_iter().map(|x| x.into_cpp()).collect(),
233                    output: func.output.into_cpp(),
234                },
235            });
236        }
237        for impl_block in zng.extern_cpp_impls {
238            let rust_link_names = rust_file.add_extern_cpp_impl(
239                &impl_block.ty,
240                impl_block.tr.as_ref(),
241                &impl_block.methods,
242            );
243            cpp_file.exported_impls.push(CppExportedImplDefinition {
244                tr: impl_block.tr.map(|x| x.into_cpp()),
245                ty: impl_block.ty.into_cpp(),
246                methods: impl_block
247                    .methods
248                    .iter()
249                    .zip(&rust_link_names)
250                    .map(|(method, link_name)| {
251                        let (_, inputs) = real_inputs_of_method(method, &impl_block.ty);
252                        (
253                            cpp_handle_keyword(&method.name).to_owned(),
254                            CppFnSig {
255                                rust_link_name: link_name.clone(),
256                                inputs,
257                                output: method.output.into_cpp(),
258                            },
259                        )
260                    })
261                    .collect(),
262            });
263        }
264        let (h, cpp) = cpp_file.render();
265        (rust_file.text, h, cpp)
266    }
267}
268
269fn real_inputs_of_method(method: &ZngurMethod, ty: &RustType) -> (Vec<RustType>, Vec<CppType>) {
270    let receiver_type = match method.receiver {
271        ZngurMethodReceiver::Static => None,
272        ZngurMethodReceiver::Ref(m) => Some(RustType::Ref(m, Box::new(ty.clone()))),
273        ZngurMethodReceiver::Move => Some(ty.clone()),
274    };
275    let rusty_inputs = receiver_type
276        .into_iter()
277        .chain(method.inputs.clone())
278        .collect::<Vec<_>>();
279    let inputs = rusty_inputs.iter().map(|x| x.into_cpp()).collect_vec();
280    (rusty_inputs, inputs)
281}