1use heck::ToUpperCamelCase;
25use weaveffi_ir::ir::{
26 Api, CallbackDef, EnumDef, Function, ListenerDef, Module, StructDef, TypeRef,
27};
28
29use crate::abi::{
30 self, async_callback_params, async_input_params, context_param, error_out_param, lower_param,
31 lower_return, sync_signature, AbiParam, CType,
32};
33
34#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct AbiFn {
38 pub symbol: String,
40 pub params: Vec<AbiParam>,
42 pub ret: CType,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum CallShape {
50 Sync(AbiFn),
52 Async(AsyncBinding),
54 Iterator(IteratorBinding),
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct AsyncBinding {
61 pub launch: AbiFn,
64 pub callback_type: String,
67 pub callback_params: Vec<AbiParam>,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct IteratorBinding {
75 pub elem: TypeRef,
77 pub iter_tag: String,
79 pub launch: AbiFn,
81 pub next: AbiFn,
83 pub destroy_symbol: String,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct ParamBinding {
90 pub name: String,
91 pub ty: TypeRef,
92 pub mutable: bool,
93 pub doc: Option<String>,
94 pub abi: Vec<AbiParam>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct FnBinding {
101 pub name: String,
102 pub doc: Option<String>,
103 pub deprecated: Option<String>,
104 pub since: Option<String>,
105 pub cancellable: bool,
106 pub is_async: bool,
107 pub params: Vec<ParamBinding>,
109 pub ret: Option<TypeRef>,
112 pub c_base: String,
115 pub shape: CallShape,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct FieldBinding {
122 pub name: String,
123 pub doc: Option<String>,
124 pub ty: TypeRef,
125 pub getter_symbol: String,
128 pub getter_ret: CType,
130 pub getter_out_params: Vec<AbiParam>,
132 pub value_params: Vec<AbiParam>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct BuilderBinding {
140 pub builder_tag: String,
142 pub new_symbol: String,
144 pub build_symbol: String,
146 pub destroy_symbol: String,
148 pub setters: Vec<(String, String)>,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct StructBinding {
156 pub name: String,
157 pub doc: Option<String>,
158 pub c_tag: String,
160 pub fields: Vec<FieldBinding>,
161 pub create: AbiFn,
163 pub destroy_symbol: String,
165 pub builder: Option<BuilderBinding>,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct EnumBinding {
172 pub name: String,
173 pub doc: Option<String>,
174 pub c_tag: String,
176 pub variants: Vec<EnumVariantBinding>,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct EnumVariantBinding {
182 pub name: String,
183 pub value: i32,
184 pub doc: Option<String>,
185 pub c_const: String,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct CallbackBinding {
192 pub name: String,
193 pub doc: Option<String>,
194 pub c_fn_type: String,
196 pub params: Vec<ParamBinding>,
198 pub abi_params: Vec<AbiParam>,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct ListenerBinding {
205 pub name: String,
206 pub doc: Option<String>,
207 pub event_callback: String,
209 pub callback_c_fn_type: String,
211 pub register_symbol: String,
213 pub unregister_symbol: String,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct ModuleBinding {
220 pub name: String,
221 pub segments: Vec<String>,
223 pub path: String,
225 pub doc: Option<String>,
226 pub enums: Vec<EnumBinding>,
227 pub structs: Vec<StructBinding>,
228 pub callbacks: Vec<CallbackBinding>,
229 pub listeners: Vec<ListenerBinding>,
230 pub functions: Vec<FnBinding>,
231}
232
233impl ModuleBinding {
234 pub fn callback(&self, name: &str) -> Option<&CallbackBinding> {
236 self.callbacks.iter().find(|c| c.name == name)
237 }
238
239 pub fn is_empty(&self) -> bool {
241 self.enums.is_empty()
242 && self.structs.is_empty()
243 && self.callbacks.is_empty()
244 && self.listeners.is_empty()
245 && self.functions.is_empty()
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Eq)]
251pub struct BindingModel {
252 pub prefix: String,
254 pub version: String,
256 pub modules: Vec<ModuleBinding>,
258}
259
260impl BindingModel {
261 pub fn build(api: &Api, prefix: &str) -> Self {
266 let mut modules = Vec::new();
267 for m in &api.modules {
268 lower_module(m, &[], prefix, &mut modules);
269 }
270 Self {
271 prefix: prefix.to_string(),
272 version: api.version.clone(),
273 modules,
274 }
275 }
276
277 pub fn functions(&self) -> impl Iterator<Item = (&ModuleBinding, &FnBinding)> {
279 self.modules
280 .iter()
281 .flat_map(|m| m.functions.iter().map(move |f| (m, f)))
282 }
283}
284
285fn lower_module(module: &Module, parent: &[String], prefix: &str, out: &mut Vec<ModuleBinding>) {
288 let mut segments = parent.to_vec();
289 segments.push(module.name.clone());
290 let path = segments.join("_");
291
292 let enums = module
293 .enums
294 .iter()
295 .map(|e| lower_enum(e, &path, prefix))
296 .collect();
297 let structs = module
298 .structs
299 .iter()
300 .map(|s| lower_struct(s, &path, prefix))
301 .collect();
302 let callbacks: Vec<CallbackBinding> = module
303 .callbacks
304 .iter()
305 .map(|c| lower_callback(c, &path, prefix))
306 .collect();
307 let listeners = module
308 .listeners
309 .iter()
310 .map(|l| lower_listener(l, &path, prefix))
311 .collect();
312 let functions = module
313 .functions
314 .iter()
315 .map(|f| lower_function(f, &path, prefix))
316 .collect();
317
318 let doc = module.functions.iter().find_map(|f| f.doc.clone());
321
322 out.push(ModuleBinding {
323 name: module.name.clone(),
324 segments: segments.clone(),
325 path,
326 doc,
327 enums,
328 structs,
329 callbacks,
330 listeners,
331 functions,
332 });
333
334 for child in &module.modules {
335 lower_module(child, &segments, prefix, out);
336 }
337}
338
339fn lower_param_binding(p: &weaveffi_ir::ir::Param, module: &str) -> ParamBinding {
340 ParamBinding {
341 name: p.name.clone(),
342 ty: p.ty.clone(),
343 mutable: p.mutable,
344 doc: p.doc.clone(),
345 abi: lower_param(&p.name, &p.ty, module, p.mutable),
346 }
347}
348
349fn lower_enum(e: &EnumDef, path: &str, prefix: &str) -> EnumBinding {
350 let c_tag = format!("{prefix}_{path}_{}", e.name);
351 let variants = e
352 .variants
353 .iter()
354 .map(|v| EnumVariantBinding {
355 name: v.name.clone(),
356 value: v.value,
357 doc: v.doc.clone(),
358 c_const: format!("{c_tag}_{}", v.name),
359 })
360 .collect();
361 EnumBinding {
362 name: e.name.clone(),
363 doc: e.doc.clone(),
364 c_tag,
365 variants,
366 }
367}
368
369fn lower_struct(s: &StructDef, path: &str, prefix: &str) -> StructBinding {
370 let c_tag = format!("{prefix}_{path}_{}", s.name);
371
372 let fields: Vec<FieldBinding> = s
373 .fields
374 .iter()
375 .map(|f| {
376 let r = lower_return(&f.ty, path);
377 FieldBinding {
378 name: f.name.clone(),
379 doc: f.doc.clone(),
380 ty: f.ty.clone(),
381 getter_symbol: format!("{c_tag}_get_{}", f.name),
382 getter_ret: r.ret,
383 getter_out_params: r.out_params,
384 value_params: lower_param(&f.name, &f.ty, path, false),
385 }
386 })
387 .collect();
388
389 let mut create_params: Vec<AbiParam> = s
391 .fields
392 .iter()
393 .flat_map(|f| lower_param(&f.name, &f.ty, path, false))
394 .collect();
395 create_params.push(error_out_param());
396 let create = AbiFn {
397 symbol: format!("{c_tag}_create"),
398 params: create_params,
399 ret: CType::ptr(CType::Named(format!("{path}_{}", s.name))),
400 };
401
402 let builder = s.builder.then(|| {
403 let builder_tag = format!("{c_tag}Builder");
404 let setters = s
405 .fields
406 .iter()
407 .map(|f| (f.name.clone(), format!("{c_tag}_Builder_set_{}", f.name)))
408 .collect();
409 BuilderBinding {
410 builder_tag,
411 new_symbol: format!("{c_tag}_Builder_new"),
412 build_symbol: format!("{c_tag}_Builder_build"),
413 destroy_symbol: format!("{c_tag}_Builder_destroy"),
414 setters,
415 }
416 });
417
418 StructBinding {
419 name: s.name.clone(),
420 doc: s.doc.clone(),
421 c_tag: c_tag.clone(),
422 fields,
423 create,
424 destroy_symbol: format!("{c_tag}_destroy"),
425 builder,
426 }
427}
428
429fn lower_callback(c: &CallbackDef, path: &str, prefix: &str) -> CallbackBinding {
430 let params: Vec<ParamBinding> = c
431 .params
432 .iter()
433 .map(|p| lower_param_binding(p, path))
434 .collect();
435 let mut abi_params: Vec<AbiParam> = params.iter().flat_map(|p| p.abi.clone()).collect();
436 abi_params.push(context_param());
437 CallbackBinding {
438 name: c.name.clone(),
439 doc: c.doc.clone(),
440 c_fn_type: format!("{prefix}_{path}_{}_fn", c.name),
441 params,
442 abi_params,
443 }
444}
445
446fn lower_listener(l: &ListenerDef, path: &str, prefix: &str) -> ListenerBinding {
447 ListenerBinding {
448 name: l.name.clone(),
449 doc: l.doc.clone(),
450 event_callback: l.event_callback.clone(),
451 callback_c_fn_type: format!("{prefix}_{path}_{}_fn", l.event_callback),
452 register_symbol: format!("{prefix}_{path}_register_{}", l.name),
453 unregister_symbol: format!("{prefix}_{path}_unregister_{}", l.name),
454 }
455}
456
457fn lower_function(f: &Function, path: &str, prefix: &str) -> FnBinding {
458 let params: Vec<ParamBinding> = f
459 .params
460 .iter()
461 .map(|p| lower_param_binding(p, path))
462 .collect();
463 let c_base = format!("{prefix}_{path}_{}", f.name);
464
465 let shape = if let Some(TypeRef::Iterator(inner)) = &f.returns {
466 let pascal = f.name.to_upper_camel_case();
467 let iter_tag = format!("{prefix}_{path}_{pascal}Iterator");
468 let iter_core = format!("{path}_{pascal}Iterator");
469
470 let mut launch_params: Vec<AbiParam> = f
472 .params
473 .iter()
474 .flat_map(|p| lower_param(&p.name, &p.ty, path, p.mutable))
475 .collect();
476 launch_params.push(error_out_param());
477 let launch = AbiFn {
478 symbol: c_base.clone(),
479 params: launch_params,
480 ret: CType::ptr(CType::Named(iter_core.clone())),
481 };
482
483 let item = lower_return(inner, path);
485 let mut next_params = vec![
486 AbiParam::new("iter", CType::ptr(CType::Named(iter_core.clone()))),
487 AbiParam::new("out_item", CType::ptr(item.ret)),
488 ];
489 next_params.extend(item.out_params);
490 next_params.push(error_out_param());
491 let next = AbiFn {
492 symbol: format!("{iter_tag}_next"),
493 params: next_params,
494 ret: CType::Int32,
495 };
496
497 CallShape::Iterator(IteratorBinding {
498 elem: (**inner).clone(),
499 iter_tag: iter_tag.clone(),
500 launch,
501 next,
502 destroy_symbol: format!("{iter_tag}_destroy"),
503 })
504 } else if f.r#async {
505 let callback_type = format!("{c_base}_callback");
506 let mut launch_params = async_input_params(f, path);
507 launch_params.push(AbiParam::new(
508 "callback",
509 CType::Named(format!("{path}_{}_callback", f.name)),
510 ));
511 launch_params.push(context_param());
512 let launch = AbiFn {
513 symbol: format!("{c_base}_async"),
514 params: launch_params,
515 ret: CType::Void,
516 };
517 CallShape::Async(AsyncBinding {
518 launch,
519 callback_type,
520 callback_params: async_callback_params(f.returns.as_ref(), path),
521 })
522 } else {
523 let sig = sync_signature(&f.params, f.returns.as_ref(), path);
524 CallShape::Sync(AbiFn {
525 symbol: c_base.clone(),
526 params: sig.params,
527 ret: sig.ret,
528 })
529 };
530
531 FnBinding {
532 name: f.name.clone(),
533 doc: f.doc.clone(),
534 deprecated: f.deprecated.clone(),
535 since: f.since.clone(),
536 cancellable: f.cancellable,
537 is_async: f.r#async,
538 params,
539 ret: f.returns.clone(),
540 c_base,
541 shape,
542 }
543}
544
545pub fn iterator_item_ctype(elem: &TypeRef, module: &str) -> CType {
548 abi::lower_return(elem, module).ret
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use weaveffi_ir::ir::{
555 CallbackDef, EnumDef, EnumVariant, Function, ListenerDef, Module, Param, StructDef,
556 StructField,
557 };
558
559 fn param(name: &str, ty: TypeRef) -> Param {
560 Param {
561 name: name.into(),
562 ty,
563 mutable: false,
564 doc: None,
565 }
566 }
567
568 fn func(name: &str, params: Vec<Param>, returns: Option<TypeRef>) -> Function {
569 Function {
570 name: name.into(),
571 params,
572 returns,
573 doc: None,
574 r#async: false,
575 cancellable: false,
576 deprecated: None,
577 since: None,
578 }
579 }
580
581 fn module(name: &str) -> Module {
582 Module {
583 name: name.into(),
584 functions: vec![],
585 structs: vec![],
586 enums: vec![],
587 callbacks: vec![],
588 listeners: vec![],
589 errors: None,
590 modules: vec![],
591 }
592 }
593
594 fn api(modules: Vec<Module>) -> Api {
595 Api {
596 version: "0.3.0".into(),
597 modules,
598 generators: None,
599 package: None,
600 }
601 }
602
603 #[test]
604 fn sync_function_symbol_and_sig() {
605 let m = Module {
606 functions: vec![func(
607 "add",
608 vec![param("a", TypeRef::I32), param("b", TypeRef::I32)],
609 Some(TypeRef::I32),
610 )],
611 ..module("math")
612 };
613 let model = BindingModel::build(&api(vec![m]), "weaveffi");
614 let f = &model.modules[0].functions[0];
615 assert_eq!(f.c_base, "weaveffi_math_add");
616 match &f.shape {
617 CallShape::Sync(abi) => {
618 assert_eq!(abi.symbol, "weaveffi_math_add");
619 assert_eq!(abi.ret, CType::Int32);
620 let rendered: Vec<String> = abi
621 .params
622 .iter()
623 .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
624 .collect();
625 assert_eq!(
626 rendered,
627 ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
628 );
629 }
630 _ => panic!("expected sync"),
631 }
632 }
633
634 #[test]
635 fn prefix_is_honored_everywhere() {
636 let m = Module {
637 functions: vec![func("ping", vec![], None)],
638 ..module("net")
639 };
640 let model = BindingModel::build(&api(vec![m]), "acme");
641 let f = &model.modules[0].functions[0];
642 assert_eq!(f.c_base, "acme_net_ping");
643 }
644
645 #[test]
646 fn async_function_has_launch_and_callback() {
647 let m = Module {
648 functions: vec![Function {
649 cancellable: true,
650 r#async: true,
651 ..func(
652 "fetch",
653 vec![param("id", TypeRef::I64)],
654 Some(TypeRef::StringUtf8),
655 )
656 }],
657 ..module("net")
658 };
659 let model = BindingModel::build(&api(vec![m]), "weaveffi");
660 match &model.modules[0].functions[0].shape {
661 CallShape::Async(a) => {
662 assert_eq!(a.launch.symbol, "weaveffi_net_fetch_async");
663 assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
664 let last_two: Vec<&str> = a
665 .launch
666 .params
667 .iter()
668 .rev()
669 .take(2)
670 .map(|p| p.name.as_str())
671 .collect();
672 assert_eq!(last_two, ["context", "callback"]);
673 assert!(a.launch.params.iter().any(|p| p.name == "cancel_token"));
675 assert_eq!(a.callback_params[0].name, "context");
677 assert_eq!(a.callback_params[1].name, "err");
678 }
679 _ => panic!("expected async"),
680 }
681 }
682
683 #[test]
684 fn iterator_function_has_next_and_destroy() {
685 let m = Module {
686 functions: vec![func(
687 "get_messages",
688 vec![],
689 Some(TypeRef::Iterator(Box::new(TypeRef::StringUtf8))),
690 )],
691 ..module("events")
692 };
693 let model = BindingModel::build(&api(vec![m]), "weaveffi");
694 match &model.modules[0].functions[0].shape {
695 CallShape::Iterator(it) => {
696 assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
697 assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
698 assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
699 assert_eq!(
700 it.destroy_symbol,
701 "weaveffi_events_GetMessagesIterator_destroy"
702 );
703 assert_eq!(it.next.ret, CType::Int32);
704 let out_item = &it.next.params[1];
706 assert_eq!(out_item.name, "out_item");
707 assert_eq!(out_item.ty.render_c("weaveffi"), "const char**");
708 }
709 _ => panic!("expected iterator"),
710 }
711 }
712
713 #[test]
714 fn struct_create_getters_and_builder() {
715 let m = Module {
716 structs: vec![StructDef {
717 name: "Contact".into(),
718 doc: None,
719 fields: vec![
720 StructField {
721 name: "name".into(),
722 ty: TypeRef::StringUtf8,
723 doc: None,
724 default: None,
725 },
726 StructField {
727 name: "age".into(),
728 ty: TypeRef::I32,
729 doc: None,
730 default: None,
731 },
732 ],
733 builder: true,
734 }],
735 ..module("contacts")
736 };
737 let model = BindingModel::build(&api(vec![m]), "weaveffi");
738 let s = &model.modules[0].structs[0];
739 assert_eq!(s.c_tag, "weaveffi_contacts_Contact");
740 assert_eq!(s.create.symbol, "weaveffi_contacts_Contact_create");
741 assert_eq!(s.destroy_symbol, "weaveffi_contacts_Contact_destroy");
742 assert_eq!(
743 s.fields[0].getter_symbol,
744 "weaveffi_contacts_Contact_get_name"
745 );
746 let b = s.builder.as_ref().unwrap();
747 assert_eq!(b.builder_tag, "weaveffi_contacts_ContactBuilder");
748 assert_eq!(b.new_symbol, "weaveffi_contacts_Contact_Builder_new");
749 assert_eq!(b.setters[0].1, "weaveffi_contacts_Contact_Builder_set_name");
750 }
751
752 #[test]
753 fn enum_constants_are_prefixed() {
754 let m = Module {
755 enums: vec![EnumDef {
756 name: "Color".into(),
757 doc: None,
758 variants: vec![
759 EnumVariant {
760 name: "Red".into(),
761 value: 0,
762 doc: None,
763 },
764 EnumVariant {
765 name: "Green".into(),
766 value: 1,
767 doc: None,
768 },
769 ],
770 }],
771 ..module("gfx")
772 };
773 let model = BindingModel::build(&api(vec![m]), "weaveffi");
774 let e = &model.modules[0].enums[0];
775 assert_eq!(e.c_tag, "weaveffi_gfx_Color");
776 assert_eq!(e.variants[0].c_const, "weaveffi_gfx_Color_Red");
777 assert_eq!(e.variants[1].c_const, "weaveffi_gfx_Color_Green");
778 }
779
780 #[test]
781 fn callbacks_and_listeners_are_linked() {
782 let m = Module {
783 callbacks: vec![CallbackDef {
784 name: "on_message".into(),
785 params: vec![param("text", TypeRef::StringUtf8)],
786 doc: None,
787 }],
788 listeners: vec![ListenerDef {
789 name: "messages".into(),
790 event_callback: "on_message".into(),
791 doc: None,
792 }],
793 ..module("events")
794 };
795 let model = BindingModel::build(&api(vec![m]), "weaveffi");
796 let mb = &model.modules[0];
797 let cb = &mb.callbacks[0];
798 assert_eq!(cb.c_fn_type, "weaveffi_events_on_message_fn");
799 assert_eq!(cb.abi_params.last().unwrap().name, "context");
801 let l = &mb.listeners[0];
802 assert_eq!(l.register_symbol, "weaveffi_events_register_messages");
803 assert_eq!(l.unregister_symbol, "weaveffi_events_unregister_messages");
804 assert_eq!(l.callback_c_fn_type, "weaveffi_events_on_message_fn");
805 assert!(mb.callback("on_message").is_some());
806 }
807
808 #[test]
809 fn nested_modules_flatten_pre_order_with_paths() {
810 let inner = Module {
811 functions: vec![func("leaf_fn", vec![], None)],
812 ..module("inner")
813 };
814 let outer = Module {
815 functions: vec![func("outer_fn", vec![], None)],
816 modules: vec![inner],
817 ..module("outer")
818 };
819 let model = BindingModel::build(&api(vec![outer]), "weaveffi");
820 let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
821 assert_eq!(paths, ["outer", "outer_inner"]);
822 assert_eq!(
823 model.modules[1].functions[0].c_base,
824 "weaveffi_outer_inner_leaf_fn"
825 );
826 }
827}