1use std::collections::HashSet;
2
3use crate::{
4 ast::{Interface, Schema},
5 Database, Module,
6};
7
8pub mod js;
9pub mod rust;
10pub mod wasm;
11
12pub fn interface_init_state(
13 db: &Database,
14 interface: &Interface,
15) -> mproto_codegen::ast::TypeDef {
16 let mut init_fields = Vec::new();
17
18 for state in &interface.state {
19 init_fields.push(mproto_codegen::ast::NamedField {
20 name: state.name.clone(),
21 ty: state.ty.clone(),
22 });
23 }
24
25 for object in &interface.objects {
26 let Some(object_interface) = db.lookup_interface(&object.construct) else {
27 panic!("failed to lookup interface '{}'", object.construct.name);
28 };
29
30 if !object_interface.has_state(db) { continue; }
31
32 init_fields.push(mproto_codegen::ast::NamedField {
33 name: object.name.clone(),
34 ty: mproto_codegen::ast::Type::Defined {
35 ident: mproto_codegen::ast::QualifiedIdentifier {
37 name: format!("{}InitState", object.construct.name),
38 module: object.construct.module.clone(),
39 },
40 args: object.type_args.clone(),
41 },
42 });
43 }
44
45 let params =
46 if interface.has_state(db) {
47 interface.type_params.clone()
48 } else {
49 vec![]
50 };
51
52 mproto_codegen::ast::TypeDef {
53 name: format!("{}InitState", interface.name),
54 params,
55 body: mproto_codegen::ast::TypeBody::Struct(mproto_codegen::ast::Struct {
56 fields: init_fields,
57 }),
58 }
59}
60
61pub fn interface_role_config(
62 db: &Database,
63 mproto_cx: &mproto_codegen::codegen::CodegenCx<'_>,
64 interface: &Interface,
65 role_name: &str,
66) -> mproto_codegen::ast::TypeDef {
67 let mut fields = Vec::new();
68 let mut used_type_params = HashSet::new();
69
70 for config_list in &interface.config {
71 if !config_list.roles.iter().find(|r| *r == role_name).is_some() {
72 continue;
73 }
74
75 for config_item in &config_list.items {
76 for type_param in &interface.type_params {
77 if mproto_codegen::codegen::type_uses_param(mproto_cx, &config_item.ty, type_param) {
78 used_type_params.insert(type_param.clone());
79 }
80 }
81
82 fields.push(mproto_codegen::ast::NamedField {
83 name: config_item.name.clone(),
84 ty: config_item.ty.clone(),
85 });
86 }
87 }
88
89 for object in &interface.objects {
90 let Some(object_interface) = db.lookup_interface(&object.construct) else {
91 panic!("failed to lookup interface '{}'", object.construct.name);
92 };
93
94 let object_role_names = object.get_roles_for_parent_role(object_interface, role_name);
95 if object_role_names.is_empty() {
96 panic!("object '{}' failed to find roles '{}'", object.name, role_name);
97 };
98
99 for object_role_name in &object_role_names {
100 if !object_interface.has_config_for_role(object_role_name) { continue; }
101
102 let object_interface_role_name = &format!("{}{}", object.construct.name, object_role_name);
103 let object_role_config_def = db.mproto_db().lookup_type_def(
104 &mproto_codegen::ast::QualifiedIdentifier {
105 name: format!("{object_interface_role_name}Config"),
106 module: object.construct.module.clone(),
107 }
108 )
109 .expect("lookup object role config");
110
111 let mut type_args = vec![];
112 for (type_param, type_arg) in object_interface.type_params.iter()
113 .zip(object.type_args.iter())
114 {
115 let object_role_config_uses_type_param =
116 object_role_config_def.params.iter().find(|p| p == &type_param).is_some();
117 if object_role_config_uses_type_param {
118 type_args.push(type_arg.clone());
119 }
120 }
121
122 fields.push(mproto_codegen::ast::NamedField {
123 name: object.name.clone(),
124 ty: mproto_codegen::ast::Type::Defined {
125 ident: mproto_codegen::ast::QualifiedIdentifier {
126 name: format!("{object_interface_role_name}Config"),
127 module: object.construct.module.clone(),
128 },
129 args: type_args,
130 },
131 });
132 }
133 }
134
135 let params = interface.type_params.iter()
136 .filter(|p| used_type_params.contains(p.as_str()))
137 .cloned()
138 .collect();
139
140 mproto_codegen::ast::TypeDef {
141 name: format!("{}{}Config", interface.name, role_name),
142 params,
143 body: mproto_codegen::ast::TypeBody::Struct(mproto_codegen::ast::Struct { fields }),
144 }
145}
146
147pub fn define_interface_config_and_state_structs(
148 db: &mut Database,
149 interface: &Interface,
150) {
151 let init_state_struct = interface_init_state(db, interface);
153 let _ = db.mproto_db_mut().local_mut().new_type_def(init_state_struct);
154 for role_name in &interface.roles {
156 let role_config_struct = interface_role_config(
157 db,
158 &mproto_codegen::codegen::CodegenCx::new_with_type_params(
159 db.mproto_db(), Some("crate::proto"), true, &interface.type_params,
160 ),
161 &interface,
162 role_name,
163 );
164 let _ = db.mproto_db_mut().local_mut().new_type_def(role_config_struct);
165 }
166}
167
168pub fn load_imports_recursive(db: &mut Database, schema: &Schema) -> Result<(), ()> {
169 for import in &schema.imports {
170 if import.path.ends_with(".mproto") {
171 let type_defs = mproto_codegen::parse::parse_file(&import.path)
172 .expect(&format!("failed to parse import '{}'.", import.path));
173 let mproto_module = mproto_codegen::Module::from_type_defs(type_defs.clone());
177 db.mproto_db_mut().add_module(import.name.clone(), "mproto", mproto_module);
179 } else if import.path.ends_with(".modrpc") {
180 let mut import_schema = crate::parse::parse_file(&import.path)
181 .expect(&format!("failed to parse import '{}'.", import.path));
182
183 load_imports_recursive(db, &import_schema)?;
184
185 let mproto_module = mproto_codegen::Module::from_type_defs(
187 import_schema.type_defs.clone(),
188 );
189 let lower_cx = LowerCx { module_name: &import.name, module: &mproto_module };
190 let lowered_type_defs = import_schema.type_defs.into_iter()
191 .map(|type_def| lower_cx.lower_type_def(type_def))
192 .collect();
193 let lowered_mproto_module = mproto_codegen::Module::from_type_defs(lowered_type_defs);
194
195 for interface in &mut import_schema.interfaces {
197 for state_field in &mut interface.state {
199 state_field.ty = lower_cx.lower_type(state_field.ty.clone());
200 }
201 for config_list in &mut interface.config {
202 for config_item in &mut config_list.items {
203 config_item.ty = lower_cx.lower_type(config_item.ty.clone());
204 }
205 }
206 }
207 db.mproto_db_mut().add_module(import.name.clone(), "modrpc", lowered_mproto_module);
209 for interface in &mut import_schema.interfaces {
212 let init_state_struct = interface_init_state(db, &interface);
217 let _ = db.mproto_db_mut().imported_module_mut(&import.name)
218 .expect("lookup mproto module")
219 .new_type_def(init_state_struct);
220 for role_name in &interface.roles {
222 let role_config_struct = interface_role_config(
223 db,
224 &mproto_codegen::codegen::CodegenCx::new_with_type_params(
225 db.mproto_db(), None, true, &interface.type_params,
226 ),
227 &interface,
228 role_name,
229 );
230 let _ = db.mproto_db_mut().imported_module_mut(&import.name)
231 .expect("lookup mproto module")
232 .new_type_def(role_config_struct);
233 }
234 }
235
236 let mut module = Module::new();
238 for interface in import_schema.interfaces {
239 let _ = module.add_interface(interface.clone());
240 }
241 db.add_module(import.name.clone(), module);
243 } else {
244 panic!("imports must end with '.mproto' or '.modrpc' - got '{}'", import.path);
246 }
247 }
248
249 Ok(())
250}
251
252struct LowerCx<'a> {
253 module_name: &'a str,
254 module: &'a mproto_codegen::Module,
255}
256
257impl<'a> LowerCx<'a> {
258 fn lower_type_def(
262 &self,
263 type_def: mproto_codegen::ast::TypeDef,
264 ) -> mproto_codegen::ast::TypeDef {
265 let body = match type_def.body {
266 mproto_codegen::ast::TypeBody::Struct(struct_body) => {
267 mproto_codegen::ast::TypeBody::Struct(mproto_codegen::ast::Struct {
268 fields: self.lower_named_fields(struct_body.fields),
269 })
270 }
271 mproto_codegen::ast::TypeBody::Enum(enum_body) => {
272 mproto_codegen::ast::TypeBody::Enum(mproto_codegen::ast::Enum {
273 variants: enum_body.variants.into_iter().map(|(name, variant)| {
274 let variant = match variant {
275 mproto_codegen::ast::EnumVariant::Empty => mproto_codegen::ast::EnumVariant::Empty,
276 mproto_codegen::ast::EnumVariant::NamedFields { fields } => {
277 mproto_codegen::ast::EnumVariant::NamedFields {
278 fields: self.lower_named_fields(fields),
279 }
280 }
281 };
282 (name, variant)
283 })
284 .collect()
285 })
286 }
287 };
288
289 mproto_codegen::ast::TypeDef {
290 name: type_def.name,
291 params: type_def.params,
292 body,
293 }
294 }
295
296 fn lower_named_fields(
297 &self,
298 fields: Vec<mproto_codegen::ast::NamedField>,
299 ) -> Vec<mproto_codegen::ast::NamedField> {
300 fields.into_iter().map(|f| {
301 mproto_codegen::ast::NamedField {
302 name: f.name,
303 ty: self.lower_type(f.ty),
304 }
305 })
306 .collect()
307 }
308
309 fn lower_type(&self, ty: mproto_codegen::ast::Type) -> mproto_codegen::ast::Type {
310 use mproto_codegen::ast::{Type, PrimitiveType, QualifiedIdentifier};
311 match ty {
312 Type::Primitive(PrimitiveType::Box(inner_ty)) => {
313 Type::Primitive(PrimitiveType::Box(Box::new(self.lower_type(*inner_ty))))
314 }
315 Type::Primitive(PrimitiveType::List(item_ty)) => {
316 Type::Primitive(PrimitiveType::List(Box::new(self.lower_type(*item_ty))))
317 }
318 Type::Primitive(PrimitiveType::Option(item_ty)) => {
319 Type::Primitive(PrimitiveType::Option(Box::new(self.lower_type(*item_ty))))
320 }
321 Type::Primitive(PrimitiveType::Result(ok_ty, err_ty)) => {
322 Type::Primitive(PrimitiveType::Result(
323 Box::new(self.lower_type(*ok_ty)),
324 Box::new(self.lower_type(*err_ty)),
325 ))
326 }
327 Type::Defined { ident, args } => {
328 let args = args.into_iter().map(|a| self.lower_type(a)).collect();
329 if ident.module.is_none() && self.module.type_def_by_name(&ident.name).is_some() {
330 Type::Defined {
331 ident: QualifiedIdentifier {
332 module: Some(self.module_name.into()),
333 name: ident.name,
334 },
335 args,
336 }
337 } else {
338 Type::Defined { ident, args }
339 }
340 }
341 ty @ _ => ty,
342 }
343 }
344}
345
346fn write_js_file(
347 path: impl AsRef<std::path::Path> + std::fmt::Debug,
348 header: &str,
349 tokens: &genco::lang::js::Tokens,
350) -> std::io::Result<()> {
351 use std::io::Write;
352
353 let fmt = genco::fmt::Config::from_lang::<genco::lang::JavaScript>()
354 .with_indentation(genco::fmt::Indentation::Space(2));
355 let config = genco::lang::js::Config::default();
356 let mut file = std::fs::File::create(&path)?;
357
358 file.write_all(header.as_bytes())?;
359
360 let mut w = genco::fmt::IoWriter::new(file);
361 tokens
362 .format_file(&mut w.as_formatter(&fmt), &config)
363 .expect(&format!("format {:?} file", path));
364
365 Ok(())
366}