1use alloc::collections::BTreeSet;
21use alloc::format;
22use alloc::string::{String, ToString};
23use alloc::vec::Vec;
24
25use zerodds_idl::ast::{
26 AttrDecl, Export, Identifier, InterfaceDef, InterfaceKind, OpDecl, ParamAttribute, ParamDecl,
27 ScopedName, TypeSpec,
28};
29use zerodds_idl::errors::Span;
30
31#[derive(Debug, Clone, PartialEq)]
33pub struct Ami4CcmInterfaces {
34 pub reply_handler: InterfaceDef,
36 pub ami_interface: InterfaceDef,
38}
39
40#[derive(Debug, Clone, Default)]
52pub struct TransformContext {
53 pub known_bases: BTreeSet<String>,
59 pub known_symbols: BTreeSet<String>,
63}
64
65impl TransformContext {
66 #[must_use]
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn mark_transformed(&mut self, iface_name: &str) {
75 self.known_bases.insert(iface_name.to_string());
76 self.known_symbols.insert(format!("AMI4CCM_{iface_name}"));
77 self.known_symbols
78 .insert(format!("AMI4CCM_{iface_name}ReplyHandler"));
79 }
80
81 pub fn add_known_symbol(&mut self, name: &str) {
84 self.known_symbols.insert(name.to_string());
85 }
86}
87
88#[must_use]
95pub fn transform_interface(iface: &InterfaceDef) -> Ami4CcmInterfaces {
96 transform_interface_in_context(iface, &TransformContext::default())
97}
98
99#[must_use]
115pub fn transform_interface_in_context(
116 iface: &InterfaceDef,
117 ctx: &TransformContext,
118) -> Ami4CcmInterfaces {
119 let span = Span::SYNTHETIC;
120 let original_name = iface.name.text.clone();
121
122 let mut original_op_names: Vec<String> = Vec::new();
125 for export in &iface.exports {
126 match export {
127 Export::Op(op) => original_op_names.push(op.name.text.clone()),
128 Export::Attr(attr) => {
129 original_op_names.push(format!("get_{}", attr.name.text));
130 if !attr.readonly {
131 original_op_names.push(format!("set_{}", attr.name.text));
132 }
133 }
134 _ => {}
135 }
136 }
137
138 let reply_handler_base = iface
143 .bases
144 .iter()
145 .find_map(|b| {
146 b.parts
147 .last()
148 .map(|i| &i.text)
149 .filter(|n| ctx.known_bases.contains(n.as_str()))
150 .cloned()
151 })
152 .map_or_else(
153 || scoped_name(&["CCM_AMI", "ReplyHandler"], span),
154 |base| ScopedName::single(Identifier::new(format!("AMI4CCM_{base}ReplyHandler"), span)),
155 );
156
157 let ami_iface_name =
161 resolve_unique_iface_name(&format!("AMI4CCM_{original_name}"), &ctx.known_symbols);
162 let reply_handler_name = resolve_unique_iface_name(
163 &format!("AMI4CCM_{original_name}ReplyHandler"),
164 &ctx.known_symbols,
165 );
166
167 let reply_handler = build_reply_handler(
168 &original_name,
169 &reply_handler_name,
170 reply_handler_base,
171 &iface.exports,
172 &original_op_names,
173 span,
174 );
175 let ami_interface = build_ami_interface(
176 &original_name,
177 &ami_iface_name,
178 &iface.exports,
179 &original_op_names,
180 span,
181 );
182
183 Ami4CcmInterfaces {
184 reply_handler,
185 ami_interface,
186 }
187}
188
189fn resolve_unique_iface_name(base: &str, known: &BTreeSet<String>) -> String {
196 let mut candidate = base.to_string();
197 for _ in 0..16 {
198 if !known.contains(&candidate) {
199 return candidate;
200 }
201 candidate = format!("AMI_{candidate}");
202 }
203 candidate
204}
205
206fn build_ami_interface(
209 original_name: &str,
210 iface_name: &str,
211 exports: &[Export],
212 original_op_names: &[String],
213 span: Span,
214) -> InterfaceDef {
215 let handler_type = handler_type_spec(original_name);
216 let mut emitted: Vec<String> = Vec::new();
219 let mut new_exports: Vec<Export> = Vec::new();
220
221 for export in exports {
222 match export {
223 Export::Op(op) => {
224 let name = resolve_sendc_name(&op.name.text, original_op_names, &emitted);
225 emitted.push(name.clone());
226 new_exports.push(Export::Op(build_sendc_op(&name, &handler_type, op, span)));
227 }
228 Export::Attr(attr) => {
229 let getter_name = resolve_sendc_name(
231 &format!("get_{}", attr.name.text),
232 original_op_names,
233 &emitted,
234 );
235 emitted.push(getter_name.clone());
236 new_exports.push(Export::Op(build_sendc_attr_get(
237 &getter_name,
238 &handler_type,
239 span,
240 )));
241 if !attr.readonly {
242 let setter_name = resolve_sendc_name(
243 &format!("set_{}", attr.name.text),
244 original_op_names,
245 &emitted,
246 );
247 emitted.push(setter_name.clone());
248 new_exports.push(Export::Op(build_sendc_attr_set(
249 &setter_name,
250 &handler_type,
251 attr,
252 span,
253 )));
254 }
255 }
256 _ => {}
260 }
261 }
262
263 InterfaceDef {
264 kind: InterfaceKind::Local,
265 name: Identifier::new(iface_name, span),
266 bases: Vec::new(),
267 exports: new_exports,
268 annotations: Vec::new(),
269 span,
270 }
271}
272
273fn build_reply_handler(
276 original_name: &str,
277 handler_name: &str,
278 handler_base: ScopedName,
279 exports: &[Export],
280 original_op_names: &[String],
281 span: Span,
282) -> InterfaceDef {
283 let _ = original_name; let exc_holder_type = exception_holder_type_spec();
285 let mut emitted: Vec<String> = Vec::new();
286 let mut new_exports: Vec<Export> = Vec::new();
287
288 for export in exports {
289 match export {
290 Export::Op(op) => {
291 let normal_name = op.name.text.clone();
293 emitted.push(normal_name.clone());
294 new_exports.push(Export::Op(build_handler_normal_op(&normal_name, op, span)));
295 let excep_name = resolve_excep_name(&op.name.text, original_op_names, &emitted);
297 emitted.push(excep_name.clone());
298 new_exports.push(Export::Op(build_handler_excep_op(
299 &excep_name,
300 &exc_holder_type,
301 span,
302 )));
303 }
304 Export::Attr(attr) => {
305 let get_name = format!("get_{}", attr.name.text);
307 emitted.push(get_name.clone());
308 new_exports.push(Export::Op(build_handler_attr_get(
309 &get_name,
310 &attr.type_spec,
311 span,
312 )));
313 let get_excep = resolve_excep_name(&get_name, original_op_names, &emitted);
315 emitted.push(get_excep.clone());
316 new_exports.push(Export::Op(build_handler_excep_op(
317 &get_excep,
318 &exc_holder_type,
319 span,
320 )));
321 if !attr.readonly {
322 let set_name = format!("set_{}", attr.name.text);
324 emitted.push(set_name.clone());
325 new_exports.push(Export::Op(build_handler_attr_set_ack(&set_name, span)));
326 let set_excep = resolve_excep_name(&set_name, original_op_names, &emitted);
328 emitted.push(set_excep.clone());
329 new_exports.push(Export::Op(build_handler_excep_op(
330 &set_excep,
331 &exc_holder_type,
332 span,
333 )));
334 }
335 }
336 _ => {}
337 }
338 }
339
340 InterfaceDef {
341 kind: InterfaceKind::Local,
342 name: Identifier::new(handler_name, span),
343 bases: alloc::vec![handler_base],
347 exports: new_exports,
348 annotations: Vec::new(),
349 span,
350 }
351}
352
353fn build_sendc_op(name: &str, handler_type: &TypeSpec, op: &OpDecl, span: Span) -> OpDecl {
355 let mut params = Vec::new();
356 params.push(handler_param(handler_type, span));
357 for p in &op.params {
358 if matches!(p.attribute, ParamAttribute::In | ParamAttribute::InOut) {
362 params.push(ParamDecl {
363 attribute: ParamAttribute::In,
364 type_spec: p.type_spec.clone(),
365 name: p.name.clone(),
366 annotations: Vec::new(),
367 span,
368 });
369 }
370 }
371 OpDecl {
372 name: Identifier::new(name, span),
373 oneway: false,
374 return_type: None,
375 params,
376 raises: Vec::new(),
380 annotations: Vec::new(),
381 span,
382 }
383}
384
385fn build_sendc_attr_get(name: &str, handler_type: &TypeSpec, span: Span) -> OpDecl {
387 OpDecl {
388 name: Identifier::new(name, span),
389 oneway: false,
390 return_type: None,
391 params: alloc::vec![handler_param(handler_type, span)],
392 raises: Vec::new(),
393 annotations: Vec::new(),
394 span,
395 }
396}
397
398fn build_sendc_attr_set(
401 name: &str,
402 handler_type: &TypeSpec,
403 attr: &AttrDecl,
404 span: Span,
405) -> OpDecl {
406 let arg_name = format!("attr_{}", attr.name.text);
407 OpDecl {
408 name: Identifier::new(name, span),
409 oneway: false,
410 return_type: None,
411 params: alloc::vec![
412 handler_param(handler_type, span),
413 ParamDecl {
414 attribute: ParamAttribute::In,
415 type_spec: attr.type_spec.clone(),
416 name: Identifier::new(arg_name, span),
417 annotations: Vec::new(),
418 span,
419 },
420 ],
421 raises: Vec::new(),
422 annotations: Vec::new(),
423 span,
424 }
425}
426
427fn build_handler_normal_op(name: &str, op: &OpDecl, span: Span) -> OpDecl {
430 let mut params = Vec::new();
431 if let Some(ret) = &op.return_type {
432 params.push(ParamDecl {
433 attribute: ParamAttribute::In,
434 type_spec: ret.clone(),
435 name: Identifier::new("ami_return_val", span),
436 annotations: Vec::new(),
437 span,
438 });
439 }
440 for p in &op.params {
441 if matches!(p.attribute, ParamAttribute::InOut | ParamAttribute::Out) {
444 params.push(ParamDecl {
445 attribute: ParamAttribute::In,
446 type_spec: p.type_spec.clone(),
447 name: p.name.clone(),
448 annotations: Vec::new(),
449 span,
450 });
451 }
452 }
453 OpDecl {
454 name: Identifier::new(name, span),
455 oneway: false,
456 return_type: None,
457 params,
458 raises: Vec::new(),
462 annotations: Vec::new(),
463 span,
464 }
465}
466
467fn build_handler_attr_get(name: &str, attr_type: &TypeSpec, span: Span) -> OpDecl {
469 OpDecl {
470 name: Identifier::new(name, span),
471 oneway: false,
472 return_type: None,
473 params: alloc::vec![ParamDecl {
474 attribute: ParamAttribute::In,
475 type_spec: attr_type.clone(),
476 name: Identifier::new("ami_return_val", span),
477 annotations: Vec::new(),
478 span,
479 }],
480 raises: Vec::new(),
481 annotations: Vec::new(),
482 span,
483 }
484}
485
486fn build_handler_attr_set_ack(name: &str, span: Span) -> OpDecl {
489 OpDecl {
490 name: Identifier::new(name, span),
491 oneway: false,
492 return_type: None,
493 params: Vec::new(),
494 raises: Vec::new(),
495 annotations: Vec::new(),
496 span,
497 }
498}
499
500fn build_handler_excep_op(name: &str, exc_holder_type: &TypeSpec, span: Span) -> OpDecl {
503 OpDecl {
504 name: Identifier::new(name, span),
505 oneway: false,
506 return_type: None,
507 params: alloc::vec![ParamDecl {
508 attribute: ParamAttribute::In,
509 type_spec: exc_holder_type.clone(),
510 name: Identifier::new("excep_holder", span),
511 annotations: Vec::new(),
512 span,
513 }],
514 raises: Vec::new(),
515 annotations: Vec::new(),
516 span,
517 }
518}
519
520fn resolve_sendc_name(
527 original_op_name: &str,
528 forbidden_in_iface: &[String],
529 already_emitted: &[String],
530) -> String {
531 let mut prefix = String::from("sendc_");
532 loop {
533 let candidate = format!("{prefix}{original_op_name}");
534 if !forbidden_in_iface.iter().any(|n| n == &candidate)
535 && !already_emitted.iter().any(|n| n == &candidate)
536 {
537 return candidate;
538 }
539 prefix.push_str("ami_");
540 }
541}
542
543fn resolve_excep_name(
549 original_op_name: &str,
550 forbidden_in_iface: &[String],
551 already_emitted: &[String],
552) -> String {
553 let mut suffix = String::from("_excep");
554 loop {
555 let candidate = format!("{original_op_name}{suffix}");
556 if !forbidden_in_iface.iter().any(|n| n == &candidate)
557 && !already_emitted.iter().any(|n| n == &candidate)
558 {
559 return candidate;
560 }
561 suffix = format!("_ami{suffix}");
564 }
565}
566
567fn handler_param(handler_type: &TypeSpec, span: Span) -> ParamDecl {
568 ParamDecl {
569 attribute: ParamAttribute::In,
570 type_spec: handler_type.clone(),
571 name: Identifier::new("ami_handler", span),
572 annotations: Vec::new(),
573 span,
574 }
575}
576
577fn handler_type_spec(original_name: &str) -> TypeSpec {
578 TypeSpec::Scoped(scoped_name(
579 &[&format!("AMI4CCM_{original_name}ReplyHandler")],
580 Span::SYNTHETIC,
581 ))
582}
583
584fn exception_holder_type_spec() -> TypeSpec {
585 TypeSpec::Scoped(scoped_name(
586 &["CCM_AMI", "ExceptionHolder"],
587 Span::SYNTHETIC,
588 ))
589}
590
591fn scoped_name(parts: &[&str], span: Span) -> ScopedName {
592 ScopedName {
593 absolute: false,
594 parts: parts
595 .iter()
596 .map(|p| Identifier::new((*p).to_string(), span))
597 .collect(),
598 span,
599 }
600}
601
602#[cfg(test)]
604fn assert_void_return(op: &OpDecl) {
605 assert!(
606 op.return_type.is_none(),
607 "expected void return on {}",
608 op.name.text
609 );
610}
611
612#[cfg(test)]
613#[allow(clippy::panic, clippy::expect_used)]
614mod tests {
615 use super::*;
616 use zerodds_idl::ast::{IntegerType, PrimitiveType, StringType};
617
618 fn primitive(p: PrimitiveType) -> TypeSpec {
619 TypeSpec::Primitive(p)
620 }
621
622 fn op(
623 name: &str,
624 return_type: Option<TypeSpec>,
625 params: Vec<(ParamAttribute, TypeSpec, &str)>,
626 ) -> Export {
627 let span = Span::SYNTHETIC;
628 Export::Op(OpDecl {
629 name: Identifier::new(name, span),
630 oneway: false,
631 return_type,
632 params: params
633 .into_iter()
634 .map(|(attr, ty, n)| ParamDecl {
635 attribute: attr,
636 type_spec: ty,
637 name: Identifier::new(n, span),
638 annotations: Vec::new(),
639 span,
640 })
641 .collect(),
642 raises: Vec::new(),
643 annotations: Vec::new(),
644 span,
645 })
646 }
647
648 fn attr(name: &str, ty: TypeSpec, readonly: bool) -> Export {
649 let span = Span::SYNTHETIC;
650 Export::Attr(AttrDecl {
651 name: Identifier::new(name, span),
652 type_spec: ty,
653 readonly,
654 get_raises: Vec::new(),
655 set_raises: Vec::new(),
656 annotations: Vec::new(),
657 span,
658 })
659 }
660
661 fn iface(name: &str, exports: Vec<Export>) -> InterfaceDef {
662 InterfaceDef {
663 kind: InterfaceKind::Plain,
664 name: Identifier::new(name, Span::SYNTHETIC),
665 bases: Vec::new(),
666 exports,
667 annotations: Vec::new(),
668 span: Span::SYNTHETIC,
669 }
670 }
671
672 fn string_ty() -> TypeSpec {
673 TypeSpec::String(StringType {
674 wide: false,
675 bound: None,
676 span: Span::SYNTHETIC,
677 })
678 }
679
680 fn double_ty() -> TypeSpec {
681 primitive(PrimitiveType::Floating(
682 zerodds_idl::ast::FloatingType::Double,
683 ))
684 }
685
686 fn boolean_ty() -> TypeSpec {
687 primitive(PrimitiveType::Boolean)
688 }
689
690 fn long_ty() -> TypeSpec {
691 primitive(PrimitiveType::Integer(IntegerType::Long))
692 }
693
694 #[test]
695 fn produces_two_local_interfaces_with_correct_names() {
696 let i = iface("StockManager", alloc::vec![]);
698 let out = transform_interface(&i);
699 assert_eq!(out.ami_interface.name.text, "AMI4CCM_StockManager");
700 assert_eq!(
701 out.reply_handler.name.text,
702 "AMI4CCM_StockManagerReplyHandler"
703 );
704 assert_eq!(out.ami_interface.kind, InterfaceKind::Local);
705 assert_eq!(out.reply_handler.kind, InterfaceKind::Local);
706 }
707
708 #[test]
709 fn reply_handler_inherits_from_ccm_ami_replyhandler() {
710 let i = iface("Foo", alloc::vec![]);
713 let out = transform_interface(&i);
714 assert_eq!(out.reply_handler.bases.len(), 1);
715 let base = &out.reply_handler.bases[0];
716 assert_eq!(
717 base.parts
718 .iter()
719 .map(|p| p.text.as_str())
720 .collect::<Vec<_>>(),
721 alloc::vec!["CCM_AMI", "ReplyHandler"]
722 );
723 }
724
725 #[test]
726 fn sendc_op_has_handler_first_then_in_inout_only() {
727 let i = iface(
729 "I",
730 alloc::vec![op(
731 "remove_stock",
732 None,
733 alloc::vec![
734 (ParamAttribute::In, string_ty(), "symbol"),
735 (ParamAttribute::Out, double_ty(), "quote"),
736 ],
737 )],
738 );
739 let out = transform_interface(&i);
740 let Export::Op(o) = &out.ami_interface.exports[0] else {
741 panic!("expected op");
742 };
743 assert_eq!(o.name.text, "sendc_remove_stock");
744 assert_void_return(o);
745 assert_eq!(o.params.len(), 2);
747 assert_eq!(o.params[0].name.text, "ami_handler");
748 assert_eq!(o.params[0].attribute, ParamAttribute::In);
749 assert_eq!(o.params[1].name.text, "symbol");
750 assert_eq!(o.params[1].attribute, ParamAttribute::In);
751 }
752
753 #[test]
754 fn sendc_inout_becomes_in() {
755 let i = iface(
757 "I",
758 alloc::vec![op(
759 "find_closest_symbol",
760 Some(boolean_ty()),
761 alloc::vec![(ParamAttribute::InOut, string_ty(), "symbol")],
762 )],
763 );
764 let out = transform_interface(&i);
765 let Export::Op(o) = &out.ami_interface.exports[0] else {
766 panic!()
767 };
768 assert_eq!(o.params.len(), 2);
769 assert_eq!(o.params[1].attribute, ParamAttribute::In);
771 assert_eq!(o.params[1].name.text, "symbol");
772 }
773
774 #[test]
775 fn handler_op_has_return_value_then_inout_out() {
776 let i = iface(
778 "I",
779 alloc::vec![op(
780 "remove_stock",
781 Some(double_ty()),
782 alloc::vec![
783 (ParamAttribute::In, string_ty(), "symbol"),
784 (ParamAttribute::Out, double_ty(), "quote"),
785 ],
786 )],
787 );
788 let out = transform_interface(&i);
789 let Export::Op(o) = &out.reply_handler.exports[0] else {
792 panic!()
793 };
794 assert_eq!(o.name.text, "remove_stock");
795 assert_eq!(o.params.len(), 2);
797 assert_eq!(o.params[0].name.text, "ami_return_val");
798 assert_eq!(o.params[0].attribute, ParamAttribute::In);
799 assert_eq!(o.params[1].name.text, "quote");
800 assert_eq!(o.params[1].attribute, ParamAttribute::In);
801 }
802
803 #[test]
804 fn handler_excep_op_takes_exception_holder() {
805 let i = iface(
808 "I",
809 alloc::vec![op("get_quote", Some(double_ty()), alloc::vec![])],
810 );
811 let out = transform_interface(&i);
812 let Export::Op(o) = &out.reply_handler.exports[1] else {
814 panic!()
815 };
816 assert_eq!(o.name.text, "get_quote_excep");
817 assert_eq!(o.params.len(), 1);
818 assert_eq!(o.params[0].name.text, "excep_holder");
819 let TypeSpec::Scoped(sn) = &o.params[0].type_spec else {
820 panic!("expected ScopedName for ExceptionHolder")
821 };
822 assert_eq!(
823 sn.parts.iter().map(|p| p.text.as_str()).collect::<Vec<_>>(),
824 alloc::vec!["CCM_AMI", "ExceptionHolder"]
825 );
826 }
827
828 #[test]
829 fn attribute_get_set_generated_in_both_interfaces() {
830 let i = iface(
833 "I",
834 alloc::vec![attr("stock_exchange_name", string_ty(), false)],
835 );
836 let out = transform_interface(&i);
837
838 let names: Vec<String> = out
840 .ami_interface
841 .exports
842 .iter()
843 .map(|e| match e {
844 Export::Op(o) => o.name.text.clone(),
845 _ => String::new(),
846 })
847 .collect();
848 assert!(names.contains(&String::from("sendc_get_stock_exchange_name")));
849 assert!(names.contains(&String::from("sendc_set_stock_exchange_name")));
850
851 let h_names: Vec<String> = out
853 .reply_handler
854 .exports
855 .iter()
856 .map(|e| match e {
857 Export::Op(o) => o.name.text.clone(),
858 _ => String::new(),
859 })
860 .collect();
861 assert!(h_names.contains(&String::from("get_stock_exchange_name")));
862 assert!(h_names.contains(&String::from("get_stock_exchange_name_excep")));
863 assert!(h_names.contains(&String::from("set_stock_exchange_name")));
864 assert!(h_names.contains(&String::from("set_stock_exchange_name_excep")));
865 }
866
867 #[test]
868 fn readonly_attribute_only_generates_getter() {
869 let i = iface("I", alloc::vec![attr("price", double_ty(), true)]);
872 let out = transform_interface(&i);
873 let names: Vec<String> = out
874 .ami_interface
875 .exports
876 .iter()
877 .map(|e| match e {
878 Export::Op(o) => o.name.text.clone(),
879 _ => String::new(),
880 })
881 .collect();
882 assert!(names.contains(&String::from("sendc_get_price")));
883 assert!(!names.iter().any(|n| n.starts_with("sendc_set_")));
884 }
885
886 #[test]
887 fn sendc_attr_setter_takes_attr_prefixed_arg() {
888 let i = iface(
890 "I",
891 alloc::vec![attr("stock_exchange_name", string_ty(), false)],
892 );
893 let out = transform_interface(&i);
894 let Export::Op(o) = &out.ami_interface.exports[1] else {
896 panic!()
897 };
898 assert_eq!(o.name.text, "sendc_set_stock_exchange_name");
899 assert_eq!(o.params.len(), 2);
900 assert_eq!(o.params[1].name.text, "attr_stock_exchange_name");
901 }
902
903 #[test]
904 fn handler_attr_setter_ack_has_no_args() {
905 let i = iface(
907 "I",
908 alloc::vec![attr("stock_exchange_name", string_ty(), false)],
909 );
910 let out = transform_interface(&i);
911 let Export::Op(o) = &out.reply_handler.exports[2] else {
913 panic!()
914 };
915 assert_eq!(o.name.text, "set_stock_exchange_name");
916 assert!(o.params.is_empty());
917 }
918
919 #[test]
920 fn naming_conflict_resolved_with_ami_prefix() {
921 let i = iface(
924 "I",
925 alloc::vec![
926 op("foo", None, alloc::vec![]),
927 op("sendc_foo", None, alloc::vec![]),
928 ],
929 );
930 let out = transform_interface(&i);
931 let names: Vec<String> = out
932 .ami_interface
933 .exports
934 .iter()
935 .map(|e| match e {
936 Export::Op(o) => o.name.text.clone(),
937 _ => String::new(),
938 })
939 .collect();
940 assert!(names.contains(&String::from("sendc_ami_foo")));
942 assert!(names.contains(&String::from("sendc_sendc_foo")));
943 }
944
945 #[test]
946 fn excep_naming_conflict_resolved_with_ami_suffix() {
947 let i = iface(
949 "I",
950 alloc::vec![
951 op("foo", None, alloc::vec![]),
952 op("foo_excep", None, alloc::vec![]),
953 ],
954 );
955 let out = transform_interface(&i);
956 let h_names: Vec<String> = out
957 .reply_handler
958 .exports
959 .iter()
960 .map(|e| match e {
961 Export::Op(o) => o.name.text.clone(),
962 _ => String::new(),
963 })
964 .collect();
965 assert!(h_names.contains(&String::from("foo_ami_excep")));
967 assert!(h_names.contains(&String::from("foo_excep_excep")));
970 }
971
972 #[test]
973 fn full_stockmanager_running_example_yields_spec_signatures() {
974 let i = iface(
977 "StockManager",
978 alloc::vec![
979 attr("stock_exchange_name", string_ty(), false),
980 op(
981 "set_stock",
982 None,
983 alloc::vec![
984 (ParamAttribute::In, string_ty(), "symbol"),
985 (ParamAttribute::In, double_ty(), "new_quote"),
986 ],
987 ),
988 op(
989 "remove_stock",
990 None,
991 alloc::vec![
992 (ParamAttribute::In, string_ty(), "symbol"),
993 (ParamAttribute::Out, double_ty(), "quote"),
994 ],
995 ),
996 op(
997 "find_closest_symbol",
998 Some(boolean_ty()),
999 alloc::vec![(ParamAttribute::InOut, string_ty(), "symbol")],
1000 ),
1001 op(
1002 "get_quote",
1003 Some(double_ty()),
1004 alloc::vec![(ParamAttribute::In, string_ty(), "symbol")],
1005 ),
1006 ],
1007 );
1008 let out = transform_interface(&i);
1009
1010 let ami_names: Vec<String> = out
1012 .ami_interface
1013 .exports
1014 .iter()
1015 .map(|e| match e {
1016 Export::Op(o) => o.name.text.clone(),
1017 _ => String::new(),
1018 })
1019 .collect();
1020 for expected in [
1021 "sendc_get_stock_exchange_name",
1022 "sendc_set_stock_exchange_name",
1023 "sendc_set_stock",
1024 "sendc_remove_stock",
1025 "sendc_find_closest_symbol",
1026 "sendc_get_quote",
1027 ] {
1028 assert!(
1029 ami_names.contains(&String::from(expected)),
1030 "missing {expected} in {ami_names:?}"
1031 );
1032 }
1033
1034 let h_names: Vec<String> = out
1036 .reply_handler
1037 .exports
1038 .iter()
1039 .map(|e| match e {
1040 Export::Op(o) => o.name.text.clone(),
1041 _ => String::new(),
1042 })
1043 .collect();
1044 for expected in [
1045 "get_stock_exchange_name",
1046 "get_stock_exchange_name_excep",
1047 "set_stock_exchange_name",
1048 "set_stock_exchange_name_excep",
1049 "set_stock",
1050 "set_stock_excep",
1051 "remove_stock",
1052 "remove_stock_excep",
1053 "find_closest_symbol",
1054 "find_closest_symbol_excep",
1055 "get_quote",
1056 "get_quote_excep",
1057 ] {
1058 assert!(
1059 h_names.contains(&String::from(expected)),
1060 "missing {expected} in {h_names:?}"
1061 );
1062 }
1063 }
1064
1065 #[test]
1066 fn operation_with_no_return_no_args_yields_handler_op_with_no_params() {
1067 let i = iface("I", alloc::vec![op("acknowledge", None, alloc::vec![])]);
1069 let out = transform_interface(&i);
1070 let Export::Op(o) = &out.reply_handler.exports[0] else {
1071 panic!()
1072 };
1073 assert_eq!(o.name.text, "acknowledge");
1074 assert!(o.params.is_empty());
1075 }
1076
1077 #[test]
1078 fn long_typed_attribute_propagates_type_to_handler_param() {
1079 let i = iface("I", alloc::vec![attr("counter", long_ty(), true)]);
1080 let out = transform_interface(&i);
1081 let Export::Op(o) = &out.reply_handler.exports[0] else {
1082 panic!()
1083 };
1084 assert_eq!(o.name.text, "get_counter");
1085 assert_eq!(o.params[0].type_spec, long_ty());
1086 }
1087
1088 fn iface_with_base(name: &str, base: &str, exports: Vec<Export>) -> InterfaceDef {
1091 InterfaceDef {
1092 kind: InterfaceKind::Plain,
1093 name: Identifier::new(name, Span::SYNTHETIC),
1094 bases: alloc::vec![ScopedName::single(Identifier::new(base, Span::SYNTHETIC))],
1095 exports,
1096 annotations: Vec::new(),
1097 span: Span::SYNTHETIC,
1098 }
1099 }
1100
1101 #[test]
1102 fn derived_iface_handler_inherits_from_base_handler_when_known() {
1103 let mut ctx = TransformContext::new();
1106 ctx.mark_transformed("Base");
1107 let derived = iface_with_base(
1108 "Derived",
1109 "Base",
1110 alloc::vec![op(
1111 "ping",
1112 Some(long_ty()),
1113 alloc::vec![(ParamAttribute::In, long_ty(), "v")]
1114 )],
1115 );
1116 let out = transform_interface_in_context(&derived, &ctx);
1117 assert_eq!(out.reply_handler.bases.len(), 1);
1118 let parts = out.reply_handler.bases[0]
1119 .parts
1120 .iter()
1121 .map(|i| i.text.as_str())
1122 .collect::<Vec<_>>();
1123 assert_eq!(
1124 parts,
1125 alloc::vec!["AMI4CCM_BaseReplyHandler"],
1126 "expected derived ReplyHandler to inherit from AMI4CCM_BaseReplyHandler, got {parts:?}"
1127 );
1128 }
1129
1130 #[test]
1131 fn derived_iface_falls_back_to_ccm_ami_when_base_unknown() {
1132 let ctx = TransformContext::new();
1135 let derived = iface_with_base("Derived", "UnknownBase", alloc::vec![]);
1136 let out = transform_interface_in_context(&derived, &ctx);
1137 let parts = out.reply_handler.bases[0]
1138 .parts
1139 .iter()
1140 .map(|i| i.text.as_str())
1141 .collect::<Vec<_>>();
1142 assert_eq!(parts, alloc::vec!["CCM_AMI", "ReplyHandler"]);
1143 }
1144
1145 #[test]
1148 fn ami4ccm_prefix_collision_inserts_ami_prefix() {
1149 let mut ctx = TransformContext::new();
1153 ctx.add_known_symbol("AMI4CCM_Order");
1154 ctx.add_known_symbol("AMI4CCM_OrderReplyHandler");
1155 let i = iface("Order", alloc::vec![op("submit", None, alloc::vec![])]);
1156 let out = transform_interface_in_context(&i, &ctx);
1157 assert_eq!(out.ami_interface.name.text, "AMI_AMI4CCM_Order");
1158 assert_eq!(out.reply_handler.name.text, "AMI_AMI4CCM_OrderReplyHandler");
1159 }
1160
1161 #[test]
1162 fn ami4ccm_prefix_collision_recurses_until_unique() {
1163 let mut ctx = TransformContext::new();
1166 for layer in [
1167 "AMI4CCM_Order",
1168 "AMI_AMI4CCM_Order",
1169 "AMI_AMI_AMI4CCM_Order",
1170 ] {
1171 ctx.add_known_symbol(layer);
1172 }
1173 let i = iface("Order", alloc::vec![op("submit", None, alloc::vec![])]);
1174 let out = transform_interface_in_context(&i, &ctx);
1175 assert_eq!(out.ami_interface.name.text, "AMI_AMI_AMI_AMI4CCM_Order");
1176 }
1177
1178 #[test]
1179 fn transform_context_mark_transformed_sets_known_symbols_and_bases() {
1180 let mut ctx = TransformContext::new();
1181 ctx.mark_transformed("Foo");
1182 assert!(ctx.known_bases.contains("Foo"));
1183 assert!(ctx.known_symbols.contains("AMI4CCM_Foo"));
1184 assert!(ctx.known_symbols.contains("AMI4CCM_FooReplyHandler"));
1185 }
1186}