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 }
600 }
601
602 #[test]
603 fn sync_function_symbol_and_sig() {
604 let m = Module {
605 functions: vec![func(
606 "add",
607 vec![param("a", TypeRef::I32), param("b", TypeRef::I32)],
608 Some(TypeRef::I32),
609 )],
610 ..module("math")
611 };
612 let model = BindingModel::build(&api(vec![m]), "weaveffi");
613 let f = &model.modules[0].functions[0];
614 assert_eq!(f.c_base, "weaveffi_math_add");
615 match &f.shape {
616 CallShape::Sync(abi) => {
617 assert_eq!(abi.symbol, "weaveffi_math_add");
618 assert_eq!(abi.ret, CType::Int32);
619 let rendered: Vec<String> = abi
620 .params
621 .iter()
622 .map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
623 .collect();
624 assert_eq!(
625 rendered,
626 ["int32_t a", "int32_t b", "weaveffi_error* out_err"]
627 );
628 }
629 _ => panic!("expected sync"),
630 }
631 }
632
633 #[test]
634 fn prefix_is_honored_everywhere() {
635 let m = Module {
636 functions: vec![func("ping", vec![], None)],
637 ..module("net")
638 };
639 let model = BindingModel::build(&api(vec![m]), "acme");
640 let f = &model.modules[0].functions[0];
641 assert_eq!(f.c_base, "acme_net_ping");
642 }
643
644 #[test]
645 fn async_function_has_launch_and_callback() {
646 let m = Module {
647 functions: vec![Function {
648 cancellable: true,
649 r#async: true,
650 ..func(
651 "fetch",
652 vec![param("id", TypeRef::I64)],
653 Some(TypeRef::StringUtf8),
654 )
655 }],
656 ..module("net")
657 };
658 let model = BindingModel::build(&api(vec![m]), "weaveffi");
659 match &model.modules[0].functions[0].shape {
660 CallShape::Async(a) => {
661 assert_eq!(a.launch.symbol, "weaveffi_net_fetch_async");
662 assert_eq!(a.callback_type, "weaveffi_net_fetch_callback");
663 let last_two: Vec<&str> = a
664 .launch
665 .params
666 .iter()
667 .rev()
668 .take(2)
669 .map(|p| p.name.as_str())
670 .collect();
671 assert_eq!(last_two, ["context", "callback"]);
672 assert!(a.launch.params.iter().any(|p| p.name == "cancel_token"));
674 assert_eq!(a.callback_params[0].name, "context");
676 assert_eq!(a.callback_params[1].name, "err");
677 }
678 _ => panic!("expected async"),
679 }
680 }
681
682 #[test]
683 fn iterator_function_has_next_and_destroy() {
684 let m = Module {
685 functions: vec![func(
686 "get_messages",
687 vec![],
688 Some(TypeRef::Iterator(Box::new(TypeRef::StringUtf8))),
689 )],
690 ..module("events")
691 };
692 let model = BindingModel::build(&api(vec![m]), "weaveffi");
693 match &model.modules[0].functions[0].shape {
694 CallShape::Iterator(it) => {
695 assert_eq!(it.iter_tag, "weaveffi_events_GetMessagesIterator");
696 assert_eq!(it.launch.symbol, "weaveffi_events_get_messages");
697 assert_eq!(it.next.symbol, "weaveffi_events_GetMessagesIterator_next");
698 assert_eq!(
699 it.destroy_symbol,
700 "weaveffi_events_GetMessagesIterator_destroy"
701 );
702 assert_eq!(it.next.ret, CType::Int32);
703 let out_item = &it.next.params[1];
705 assert_eq!(out_item.name, "out_item");
706 assert_eq!(out_item.ty.render_c("weaveffi"), "const char**");
707 }
708 _ => panic!("expected iterator"),
709 }
710 }
711
712 #[test]
713 fn struct_create_getters_and_builder() {
714 let m = Module {
715 structs: vec![StructDef {
716 name: "Contact".into(),
717 doc: None,
718 fields: vec![
719 StructField {
720 name: "name".into(),
721 ty: TypeRef::StringUtf8,
722 doc: None,
723 default: None,
724 },
725 StructField {
726 name: "age".into(),
727 ty: TypeRef::I32,
728 doc: None,
729 default: None,
730 },
731 ],
732 builder: true,
733 }],
734 ..module("contacts")
735 };
736 let model = BindingModel::build(&api(vec![m]), "weaveffi");
737 let s = &model.modules[0].structs[0];
738 assert_eq!(s.c_tag, "weaveffi_contacts_Contact");
739 assert_eq!(s.create.symbol, "weaveffi_contacts_Contact_create");
740 assert_eq!(s.destroy_symbol, "weaveffi_contacts_Contact_destroy");
741 assert_eq!(
742 s.fields[0].getter_symbol,
743 "weaveffi_contacts_Contact_get_name"
744 );
745 let b = s.builder.as_ref().unwrap();
746 assert_eq!(b.builder_tag, "weaveffi_contacts_ContactBuilder");
747 assert_eq!(b.new_symbol, "weaveffi_contacts_Contact_Builder_new");
748 assert_eq!(b.setters[0].1, "weaveffi_contacts_Contact_Builder_set_name");
749 }
750
751 #[test]
752 fn enum_constants_are_prefixed() {
753 let m = Module {
754 enums: vec![EnumDef {
755 name: "Color".into(),
756 doc: None,
757 variants: vec![
758 EnumVariant {
759 name: "Red".into(),
760 value: 0,
761 doc: None,
762 },
763 EnumVariant {
764 name: "Green".into(),
765 value: 1,
766 doc: None,
767 },
768 ],
769 }],
770 ..module("gfx")
771 };
772 let model = BindingModel::build(&api(vec![m]), "weaveffi");
773 let e = &model.modules[0].enums[0];
774 assert_eq!(e.c_tag, "weaveffi_gfx_Color");
775 assert_eq!(e.variants[0].c_const, "weaveffi_gfx_Color_Red");
776 assert_eq!(e.variants[1].c_const, "weaveffi_gfx_Color_Green");
777 }
778
779 #[test]
780 fn callbacks_and_listeners_are_linked() {
781 let m = Module {
782 callbacks: vec![CallbackDef {
783 name: "on_message".into(),
784 params: vec![param("text", TypeRef::StringUtf8)],
785 doc: None,
786 }],
787 listeners: vec![ListenerDef {
788 name: "messages".into(),
789 event_callback: "on_message".into(),
790 doc: None,
791 }],
792 ..module("events")
793 };
794 let model = BindingModel::build(&api(vec![m]), "weaveffi");
795 let mb = &model.modules[0];
796 let cb = &mb.callbacks[0];
797 assert_eq!(cb.c_fn_type, "weaveffi_events_on_message_fn");
798 assert_eq!(cb.abi_params.last().unwrap().name, "context");
800 let l = &mb.listeners[0];
801 assert_eq!(l.register_symbol, "weaveffi_events_register_messages");
802 assert_eq!(l.unregister_symbol, "weaveffi_events_unregister_messages");
803 assert_eq!(l.callback_c_fn_type, "weaveffi_events_on_message_fn");
804 assert!(mb.callback("on_message").is_some());
805 }
806
807 #[test]
808 fn nested_modules_flatten_pre_order_with_paths() {
809 let inner = Module {
810 functions: vec![func("leaf_fn", vec![], None)],
811 ..module("inner")
812 };
813 let outer = Module {
814 functions: vec![func("outer_fn", vec![], None)],
815 modules: vec![inner],
816 ..module("outer")
817 };
818 let model = BindingModel::build(&api(vec![outer]), "weaveffi");
819 let paths: Vec<&str> = model.modules.iter().map(|m| m.path.as_str()).collect();
820 assert_eq!(paths, ["outer", "outer_inner"]);
821 assert_eq!(
822 model.modules[1].functions[0].c_base,
823 "weaveffi_outer_inner_leaf_fn"
824 );
825 }
826}