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