1use crate::adapter::constant;
118use crate::adapter::cst::{MaterialiseError, no_spelling_collision};
119use crate::adapter::interface;
120use crate::apply::Applied;
121use crate::default_fsharp;
122use crate::verbatim;
123use seesaw_tgg::ident::GhostId;
124use zerodds_ast::{Ast, CharWidth, PrimitiveKind, TypeKind, TypeNode};
125use zerodds_ast_core::{Id, NodeId};
126use zerodds_lang_cst::common::{Doc, Ident, Literal};
127use zerodds_lang_cst::fsharp;
128
129const LANGUAGE: &str = "F#";
131
132const INVENTED_PREFIX: &str = "ZdMapEntry_";
134
135pub const DISCRIMINATOR: &str = "_d";
143
144const MAX_MODULE_DEPTH: usize = 64;
146
147const MAX_TYPE_DEPTH: usize = 64;
158
159pub const MODULE_NAME: &str = "ZdTypes";
166
167pub fn fsharp_items(ast: &Ast, applied: &Applied) -> Result<Vec<fsharp::Decl>, MaterialiseError> {
179 let mut out = Vec::new();
180 let mut emitted: Vec<(String, String)> = Vec::new();
181
182 for (id, _) in ast.types() {
183 let start = out.len();
184 declare(ast, applied, id, &mut out, &mut emitted)?;
185 verbatim::weave(
186 verbatim::declaration_slots(ast, id, verbatim::aliases::FSHARP),
187 &fsharp_type_name(ast, id),
188 start,
189 &mut out,
190 );
191 }
192
193 for (_, node) in ast.constants() {
194 if ghost_created_for(applied, node.node_id, "FsConst").is_none() {
195 continue;
196 }
197 let value = ast
198 .eval_const(node.expr)
199 .map_err(|_| MaterialiseError::Unevaluated {
200 construct: "constant's value".to_string(),
201 at: node.name.clone(),
202 })?;
203 let declared = node.type_.resolved();
204 let type_ = match declared {
205 Some(target) => fsharp_value_type(ast, target, &node.name)?,
206 None => fsharp::Type::Primitive(fsharp::Primitive::Int32),
207 };
208 let is_long_double = declared.is_some_and(|target| {
215 matches!(
216 ast.type_(target).map(|node| node.kind.clone()),
217 Ok(TypeKind::Primitive(PrimitiveKind::Float128))
218 )
219 });
220 let name = declared_name(ast, &node.name, node.module);
221 remember(&mut emitted, &name, &node.name)?;
222 out.push(fsharp::Decl::Let(fsharp::LetDecl {
223 doc: idl_note(&name, &node.name),
224 name: Ident::new(&name),
225 params: Vec::new(),
226 type_: Some(type_.clone()),
227 body: vec![fsharp::Stmt::Return(fsharp_constant(
228 &value,
229 &type_,
230 is_long_double,
231 &node.name,
232 )?)],
233 }));
234 }
235
236 for (_, node) in ast.exceptions() {
237 if ghost_created_for(applied, node.node_id, "FsError").is_none() {
238 continue;
239 }
240 let name = declared_name(ast, &node.name, node.module);
241 remember(&mut emitted, &name, &node.name)?;
242 let mut pairs = Vec::new();
243 for member in &node.members {
244 if let Ok(member_node) = ast.member(*member) {
245 pairs.push((field_name(&member_node.name), member_node.name.clone()));
246 }
247 }
248 no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
249 let mut fields = Vec::new();
250 for member in &node.members {
251 let Ok(member_node) = ast.member(*member) else {
252 return Err(MaterialiseError::NoNode {
253 kind: "member",
254 at: crate::adapter::cst::handle_of(*member),
255 });
256 };
257 let Some(target) = member_node.type_.resolved() else {
258 return Err(MaterialiseError::Unevaluated {
259 construct: "exception member type".to_string(),
260 at: member_node.name.clone(),
261 });
262 };
263 fields.push(member_field(ast, member_node, target)?);
264 }
265 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
266 body_text: zerodds_lang_cst::common::BodyText::none(),
267 doc: with_idl_note(
268 Doc::line("An exception, as the class its members make."),
269 &name,
270 &node.name,
271 ),
272 name: Ident::new(&name),
273 body: fsharp::TypeBody::Class(fields),
274 }));
275 }
276
277 for (id, node) in ast.interfaces() {
278 if ghost_created_for(applied, node.node_id, "FsInterface").is_none() {
279 continue;
280 }
281 let name = declared_name(ast, &node.name, node.module);
282 remember(&mut emitted, &name, &node.name)?;
283 let mut fields = Vec::new();
284 let mut signatures = Vec::new();
285 let operations = interface::all_operations_flattened(ast, id)
298 .map_err(|error| interface::interface_error_to_materialise(error, &node.name))?;
299 for op in &operations {
300 let field_ident = match op.origin {
306 interface::Origin::AttributeGetter => {
307 field_name(&format!("get_{}", op.attribute_name()))
308 }
309 interface::Origin::AttributeSetter => {
310 field_name(&format!("set_{}", op.attribute_name()))
311 }
312 interface::Origin::Declared => field_name(&op.name),
313 };
314 signatures.push((field_ident.clone(), op.name.clone()));
315 let mut param_types = Vec::new();
316 for param in &op.params {
317 param_types.push(fsharp_member_type(ast, param.type_, ¶m.name)?);
318 }
319 let returns = match op.returns {
320 Some(target) => fsharp_member_type(ast, target, &op.name)?,
321 None => fsharp::Type::Primitive(fsharp::Primitive::Unit),
322 };
323 let fn_type = fsharp::Type::Fn {
324 params: param_types,
325 returns: Box::new(returns.clone()),
326 };
327 let mut doc = idl_note(&field_ident, &op.name);
328 if let Some(line) = op.raises_line() {
329 doc.push(&line);
330 }
331 fields.push(fsharp::ClassField {
332 doc,
333 name: Ident::new(&field_ident),
334 type_: fn_type,
335 default: interface_default(&returns),
336 });
337 }
338 no_spelling_collision(as_pairs(&signatures), LANGUAGE, &name)?;
339 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
340 body_text: zerodds_lang_cst::common::BodyText::none(),
341 doc: with_idl_note(
342 Doc::lines(&[
343 "An interface, as the class of function-valued fields its",
344 "operations and attributes make — an attribute a get_/set_ pair of",
345 "fields, `readonly` only the first. The transport is the RPC arc's",
346 "business; the shape is the IDL's.",
347 "`raises`/`getraises`/`setraises` are documented on the field they",
348 "belong to.",
349 ]),
350 &name,
351 &node.name,
352 ),
353 name: Ident::new(&name),
354 body: fsharp::TypeBody::Class(fields),
355 }));
356 }
357
358 Ok(verbatim::weave_file(ast, verbatim::aliases::FSHARP, out))
359}
360
361impl verbatim::Carrier for fsharp::Decl {
365 fn verbatim(text: String) -> Self {
366 Self::Verbatim(text)
367 }
368
369 fn take_into_body(
370 &mut self,
371 name: &str,
372 begin: &mut Vec<String>,
373 end: &mut Vec<String>,
374 ) -> bool {
375 let Self::Type(node) = self else {
376 return false;
377 };
378 if node.name.as_str() != name || !matches!(node.body, fsharp::TypeBody::Class(_)) {
379 return false;
380 }
381 node.body_text.begin.append(begin);
382 node.body_text.end.append(end);
383 true
384 }
385}
386
387fn interface_default(returns: &fsharp::Type) -> fsharp::Expr {
392 let _ = returns;
393 fsharp::Expr::call(
394 "fun _ -> failwith",
395 vec![fsharp::Expr::Literal(Literal::string(
396 "an interface field with no implementation bound to it",
397 ))],
398 )
399}
400
401pub fn fsharp_file(ast: &Ast, applied: &Applied) -> Result<fsharp::File, MaterialiseError> {
409 let decls = fsharp_items(ast, applied)?;
410 Ok(fsharp::File {
411 doc: Doc::lines(&[
412 "Generated by ZeroDDS from an IDL source. Do not edit.",
413 "",
414 "The IDL spelling of every renamed name is in the comment above it.",
415 "Nothing is renamed for style; a rename here is always an escape: an F#",
416 "keyword used as a member or a declaration. A member's ID under",
417 "@autoid(HASH) is the MD5 of its IDL name (DDS-XTypes 1.3 7.3.1.2.1.1)",
418 "and the escape does not invert, which is why the comment is there.",
419 ]),
420 module: Ident::new(MODULE_NAME),
421 decls,
422 })
423}
424
425#[allow(clippy::too_many_lines)]
427fn declare(
428 ast: &Ast,
429 applied: &Applied,
430 id: Id<TypeNode>,
431 out: &mut Vec<fsharp::Decl>,
432 emitted: &mut Vec<(String, String)>,
433) -> Result<(), MaterialiseError> {
434 let Ok(node) = ast.type_(id) else {
441 return Err(MaterialiseError::NoNode {
442 kind: "type",
443 at: crate::adapter::cst::handle_of(id),
444 });
445 };
446
447 match &node.kind {
448 TypeKind::Primitive(_)
449 | TypeKind::String { .. }
450 | TypeKind::Fixed { .. }
451 | TypeKind::Sequence { .. }
452 | TypeKind::Array { .. } => {}
453
454 TypeKind::Map { key, value, .. } => {
455 let (Some(key), Some(value)) = (key.resolved(), value.resolved()) else {
456 return Err(MaterialiseError::Unevaluated {
457 construct: "map key or value".to_string(),
458 at: fsharp_type_name(ast, id),
459 });
460 };
461 let name = map_entry_name_at(ast, id, 0)?;
462 if emitted.iter().any(|(spelled, _)| *spelled == name) {
463 return Ok(());
464 }
465 emitted.push((name.clone(), name.clone()));
466 let key_default =
467 default_fsharp::default_expr(ast, key).map_err(|error| no_form(&error, &name))?;
468 let value_default =
469 default_fsharp::default_expr(ast, value).map_err(|error| no_form(&error, &name))?;
470 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
471 body_text: zerodds_lang_cst::common::BodyText::none(),
472 doc: Doc::lines(&[
473 "One entry of a map. DDS-XTypes 1.3 7.4.1.1.4 defines a map as a",
474 "sequence of these, and the rules write the two halves with no",
475 "framing between them.",
476 ]),
477 name: Ident::new(&name),
478 body: fsharp::TypeBody::Class(vec![
479 fsharp::ClassField {
480 doc: Doc::none(),
481 name: Ident::new("Key"),
482 type_: fsharp_member_type(ast, key, &name)?,
483 default: key_default,
484 },
485 fsharp::ClassField {
486 doc: Doc::none(),
487 name: Ident::new("Value"),
488 type_: fsharp_member_type(ast, value, &name)?,
489 default: value_default,
490 },
491 ]),
492 }));
493 }
494
495 TypeKind::Alias { aliased } => {
496 if node.name.is_empty() {
497 return Ok(());
498 }
499 if ghost_created_for(applied, node.node_id, "FsAlias").is_none() {
500 return Ok(());
501 }
502 let Some(target) = aliased.resolved() else {
503 return Err(MaterialiseError::Unevaluated {
504 construct: "typedef target".to_string(),
505 at: node.name.clone(),
506 });
507 };
508 let name = fsharp_type_name(ast, id);
509 remember(emitted, &name, &node.name)?;
510 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
514 body_text: zerodds_lang_cst::common::BodyText::none(),
515 doc: idl_note(&name, &node.name),
516 name: Ident::new(&name),
517 body: fsharp::TypeBody::Alias(fsharp_member_type(ast, target, &node.name)?),
518 }));
519 }
520
521 TypeKind::Enumeration { enumerators } => {
522 if ghost_created_for(applied, node.node_id, "FsEnum").is_none() {
523 return Ok(());
524 }
525 let name = fsharp_type_name(ast, id);
526 remember(emitted, &name, &node.name)?;
527 let mut pairs = Vec::new();
528 for enumerator in enumerators {
529 if let Ok(enum_node) = ast.enumerator(*enumerator) {
530 pairs.push((field_name(&enum_node.name), enum_node.name.clone()));
531 }
532 }
533 no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
534 let mut variants = Vec::new();
535 for enumerator in enumerators {
536 let Ok(enum_node) = ast.enumerator(*enumerator) else {
537 return Err(MaterialiseError::NoNode {
538 kind: "enumerator",
539 at: crate::adapter::cst::handle_of(*enumerator),
540 });
541 };
542 if ghost_created_for(applied, enum_node.node_id, "FsVariant").is_none() {
543 continue;
544 }
545 let value = ast.effective_enumerator_value(*enumerator).ok_or_else(|| {
546 MaterialiseError::Unevaluated {
547 construct: "enumerator value".to_string(),
548 at: enum_node.name.clone(),
549 }
550 })?;
551 let spelled = field_name(&enum_node.name);
552 variants.push(fsharp::Variant {
553 doc: idl_note(&spelled, &enum_node.name),
554 name: Ident::new(&spelled),
555 value: fsharp::Expr::int(value),
556 });
557 }
558 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
559 body_text: zerodds_lang_cst::common::BodyText::none(),
560 doc: with_idl_note(
561 Doc::lines(&[
562 "An enumeration. The backing type is signed because DDS-XTypes",
563 "1.3 7.4.3.5.3 makes it so; the width on the wire is the",
564 "@bit_bound's and is applied by the runtime.",
565 ]),
566 &name,
567 &node.name,
568 ),
569 name: Ident::new(&name),
570 body: fsharp::TypeBody::Enum {
571 backing: fsharp::Type::Primitive(fsharp::Primitive::Int32),
572 variants,
573 },
574 }));
575 }
576
577 TypeKind::Bitmask { bits } => {
578 if ghost_created_for(applied, node.node_id, "FsBitmask").is_none() {
579 return Ok(());
580 }
581 let name = fsharp_type_name(ast, id);
582 remember(emitted, &name, &node.name)?;
583 let bound = ast.effective_bit_bound(id);
584 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
585 body_text: zerodds_lang_cst::common::BodyText::none(),
586 doc: with_idl_note(
587 Doc::lines(&[
588 "A bitmask, as the unsigned type abbreviation Table 33 of",
589 "DDS-XTypes 1.3 7.4.1.1.3.2 gives for its bound. Not an F#",
590 "[<Flags>] enum: a bit's place is its @position, which admits",
591 "gaps and any order, and an enum's own layout would say nothing",
592 "about it that these constants do not already say.",
593 ]),
594 &name,
595 &node.name,
596 ),
597 name: Ident::new(&name),
598 body: fsharp::TypeBody::Alias(fsharp::Type::Primitive(fsharp_holder(bound))),
599 }));
600 let prefix = module_prefix(ast, id);
601 for bit in bits {
602 let Ok(bit_node) = ast.bit_value(*bit) else {
603 return Err(MaterialiseError::NoNode {
604 kind: "bit",
605 at: crate::adapter::cst::handle_of(*bit),
606 });
607 };
608 if ghost_created_for(applied, bit_node.node_id, "FsBitConst").is_none() {
609 continue;
610 }
611 let position = ast.effective_bit_position(*bit).ok_or_else(|| {
612 MaterialiseError::Unevaluated {
613 construct: "bit position".to_string(),
614 at: bit_node.name.clone(),
615 }
616 })?;
617 let constant = fsharp::escape_decl(&format!("{prefix}{}", bit_node.name));
618 remember(emitted, &constant, &bit_node.name)?;
619 let shifted = fsharp::Expr::Binary {
628 op: "<<<",
629 lhs: Box::new(fsharp::Expr::int(1)),
630 rhs: Box::new(fsharp::Expr::int(i128::from(position))),
631 };
632 out.push(fsharp::Decl::Let(fsharp::LetDecl {
633 doc: idl_note(&constant, &bit_node.name),
634 name: Ident::new(&constant),
635 params: Vec::new(),
636 type_: Some(fsharp::Type::named(&name)),
637 body: vec![fsharp::Stmt::Return(typed_int(
638 fsharp_holder(bound),
639 shifted,
640 ))],
641 }));
642 }
643 }
644
645 TypeKind::Bitset { .. } => {
646 if ghost_created_for(applied, node.node_id, "FsBitset").is_none() {
647 return Ok(());
648 }
649 let name = fsharp_type_name(ast, id);
650 remember(emitted, &name, &node.name)?;
651 let mut pairs = Vec::new();
652 for field in &ast.all_bitfields_of(id) {
653 let Ok(field_node) = ast.bitfield(*field) else {
659 return Err(MaterialiseError::NoNode {
660 kind: "bitfield",
661 at: crate::adapter::cst::handle_of(*field),
662 });
663 };
664 let Some(field_ident) = field_node.name.as_ref() else {
667 continue;
668 };
669 pairs.push((field_name(field_ident), field_ident.clone()));
670 }
671 no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
672 let mut fields = Vec::new();
673 let mut index = 0;
674 for field in &ast.all_bitfields_of(id) {
675 let Ok(field_node) = ast.bitfield(*field) else {
676 return Err(MaterialiseError::NoNode {
677 kind: "bitfield",
678 at: crate::adapter::cst::handle_of(*field),
679 });
680 };
681 let Some(field_ident) = field_node.name.as_ref() else {
682 continue;
683 };
684 let _ = field_ident;
685 fields.push(fsharp::ClassField {
686 doc: idl_note(
687 &pairs[index].0,
688 &field_node.name.clone().unwrap_or_default(),
689 ),
690 name: Ident::new(&pairs[index].0),
691 type_: fsharp::Type::Primitive(fsharp_holder(field_node.bits)),
692 default: typed_int(fsharp_holder(field_node.bits), fsharp::Expr::int(0)),
693 });
694 index += 1;
695 }
696 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
697 body_text: zerodds_lang_cst::common::BodyText::none(),
698 doc: with_idl_note(
699 Doc::lines(&[
700 "A bitset. The fields are separate integers here and not packed",
701 "into one holder, because the order on the wire is the",
702 "declaration's and the packing into the anchor is the codec's.",
703 ]),
704 &name,
705 &node.name,
706 ),
707 name: Ident::new(&name),
708 body: fsharp::TypeBody::Class(fields),
709 }));
710 }
711
712 TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
713 let created = if matches!(node.kind, TypeKind::Valuetype { .. }) {
714 "FsValue"
715 } else {
716 "FsClass"
717 };
718 if ghost_created_for(applied, node.node_id, created).is_none() {
719 return Ok(());
720 }
721 let name = fsharp_type_name(ast, id);
722 remember(emitted, &name, &node.name)?;
723 let members = ast.members_of(id).to_vec();
725 let base = super::c::struct_base(ast, id);
726 let mut pairs = Vec::new();
727 if base.is_some() {
728 pairs.push((field_name("parent"), "parent".to_string()));
729 }
730 for member in &members {
731 if let Ok(member_node) = ast.member(*member) {
732 pairs.push((field_name(&member_node.name), member_node.name.clone()));
733 }
734 }
735 no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
736 let mut fields = Vec::new();
737 if let Some(base) = base {
738 let mut doc = Doc::none();
739 doc.push("The base structure, the first member (XTypes 1.3 7.5.1.4.1.3).");
740 fields.push(fsharp::ClassField {
741 doc,
742 name: Ident::new(&field_name("parent")),
743 type_: fsharp_member_type(ast, base, "parent")?,
744 default: default_fsharp::default_expr(ast, base)
745 .map_err(|error| no_form(&error, "parent"))?,
746 });
747 }
748 for member in &members {
749 let Ok(member_node) = ast.member(*member) else {
755 return Err(MaterialiseError::NoNode {
756 kind: "member",
757 at: crate::adapter::cst::handle_of(*member),
758 });
759 };
760 let Some(target) = member_node.type_.resolved() else {
761 return Err(MaterialiseError::Unevaluated {
762 construct: "member type".to_string(),
763 at: member_node.name.clone(),
764 });
765 };
766 fields.push(member_field(ast, member_node, target)?);
767 }
768 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
769 body_text: zerodds_lang_cst::common::BodyText::none(),
770 doc: idl_note(&name, &node.name),
771 name: Ident::new(&name),
772 body: fsharp::TypeBody::Class(fields),
773 }));
774 }
775
776 TypeKind::Union { discriminator, .. } => {
777 if ghost_created_for(applied, node.node_id, "FsUnion").is_none() {
778 return Ok(());
779 }
780 let name = fsharp_type_name(ast, id);
781 remember(emitted, &name, &node.name)?;
782 let Some(disc) = discriminator.resolved() else {
783 return Err(MaterialiseError::Unevaluated {
784 construct: "union discriminator".to_string(),
785 at: name,
786 });
787 };
788 let cases = ast.cases_of(id);
789 let mut pairs = vec![(DISCRIMINATOR.to_string(), "<discriminator>".to_string())];
790 for case in cases {
791 if let Ok(case_node) = ast.case(*case) {
792 if let Ok(member) = ast.member(case_node.member) {
793 pairs.push((field_name(&member.name), member.name.clone()));
794 }
795 }
796 }
797 no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
798 let disc_default = default_fsharp::discriminator_default(ast, disc)
799 .map_err(|error| no_form(&error, &name))?;
800 let mut fields = vec![fsharp::ClassField {
801 doc: Doc::line("Which branch carries the value."),
802 name: Ident::new(DISCRIMINATOR),
803 type_: fsharp_member_type(ast, disc, &name)?,
804 default: disc_default,
805 }];
806 for case in cases {
807 let Ok(case_node) = ast.case(*case) else {
816 return Err(MaterialiseError::NoNode {
817 kind: "case",
818 at: crate::adapter::cst::handle_of(*case),
819 });
820 };
821 if ghost_created_for(applied, case_node.node_id, "FsCase").is_none() {
822 continue;
823 }
824 let Ok(member) = ast.member(case_node.member) else {
825 return Err(MaterialiseError::NoNode {
826 kind: "member",
827 at: crate::adapter::cst::handle_of(case_node.member),
828 });
829 };
830 let Some(target) = member.type_.resolved() else {
831 return Err(MaterialiseError::Unevaluated {
832 construct: "union case type".to_string(),
833 at: member.name.clone(),
834 });
835 };
836 fields.push(member_field(ast, member, target)?);
837 }
838 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
839 body_text: zerodds_lang_cst::common::BodyText::none(),
840 doc: with_idl_note(
841 Doc::lines(&[
842 "A union, as a class carrying the discriminator beside every",
843 "branch. Not an F# discriminated union: a case's data *is* its",
844 "tag, and an IDL discriminator may take a value no case label",
845 "selects (IDL 4.2 7.4.1.4.4.2.3), in which state no branch is",
846 "live and only the discriminator goes on the wire",
847 "(DDS-XTypes 1.3 7.4.1.1.5.2). A discriminated union cannot be",
848 "constructed that way; this class holds it exactly. Every",
849 "branch field defaults to its own type's Table 9 value, whether",
850 "or not the default discriminator selects it — the wire never",
851 "reads an unselected branch, so any value there is as good as",
852 "any other, and a real one costs nothing an undefined one saves.",
853 ]),
854 &name,
855 &node.name,
856 ),
857 name: Ident::new(&name),
858 body: fsharp::TypeBody::Class(fields),
859 }));
860 }
861
862 TypeKind::Native => {
863 if ghost_created_for(applied, node.node_id, "FsNative").is_none() {
864 return Ok(());
865 }
866 let name = fsharp_type_name(ast, id);
867 remember(emitted, &name, &node.name)?;
868 let mapping = ast
869 .any_of_type(id)
870 .ok()
871 .and_then(|any| ast.facets(any))
872 .and_then(|facets| facets.corba.as_ref())
873 .and_then(|corba| corba.native_mapping.clone());
874 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
875 body_text: zerodds_lang_cst::common::BodyText::none(),
876 doc: with_idl_note(
877 Doc::line("A native type. `obj` unless a facet named its mapping."),
878 &name,
879 &node.name,
880 ),
881 name: Ident::new(&name),
882 body: fsharp::TypeBody::Alias(match mapping {
883 Some(spelling) => fsharp::Type::named(&spelling),
884 None => fsharp::Type::named("obj"),
885 }),
886 }));
887 }
888
889 TypeKind::ValueBox { boxed } => {
890 let Some(target) = boxed.resolved() else {
891 return Ok(());
892 };
893 let name = fsharp_type_name(ast, id);
894 if emitted.iter().any(|(spelled, _)| *spelled == name) {
895 return Ok(());
896 }
897 remember(emitted, &name, &node.name)?;
898 out.push(fsharp::Decl::Type(fsharp::TypeDecl {
902 body_text: zerodds_lang_cst::common::BodyText::none(),
903 doc: with_idl_note(
904 Doc::line("A boxed value: `None` when absent."),
905 &name,
906 &node.name,
907 ),
908 name: Ident::new(&name),
909 body: fsharp::TypeBody::Alias(fsharp::Type::option(fsharp_member_type(
910 ast, target, &name,
911 )?)),
912 }));
913 }
914
915 TypeKind::ObjectRef { .. }
916 | TypeKind::AnnotationType { .. }
917 | TypeKind::Any
918 | TypeKind::ValueBase => {}
919 }
920 Ok(())
921}
922
923fn no_form(error: &crate::encode::EncodeError, at: &str) -> MaterialiseError {
929 MaterialiseError::NoLanguageForm {
930 construct: error.to_string(),
931 language: LANGUAGE,
932 at: at.to_string(),
933 }
934}
935
936pub fn fsharp_member_type(
942 ast: &Ast,
943 target: Id<TypeNode>,
944 at: &str,
945) -> Result<fsharp::Type, MaterialiseError> {
946 let construct = match ast.type_(target).map(|node| &node.kind) {
947 Ok(TypeKind::Any) => Some("any"),
948 Ok(TypeKind::ValueBase) => Some("ValueBase"),
949 Ok(TypeKind::ObjectRef { .. }) => Some("Object"),
950 _ => None,
951 };
952 if let Some(construct) = construct {
953 return Err(MaterialiseError::NoLanguageForm {
954 construct: construct.to_string(),
955 language: LANGUAGE,
956 at: at.to_string(),
957 });
958 }
959 fsharp_value_type(ast, target, at)
960}
961
962pub fn fsharp_value_type(
970 ast: &Ast,
971 id: Id<TypeNode>,
972 at: &str,
973) -> Result<fsharp::Type, MaterialiseError> {
974 value_type_at(ast, id, at, 0)
975}
976
977fn value_type_at(
988 ast: &Ast,
989 id: Id<TypeNode>,
990 at: &str,
991 depth: usize,
992) -> Result<fsharp::Type, MaterialiseError> {
993 if depth > MAX_TYPE_DEPTH {
994 return Err(MaterialiseError::Unevaluated {
995 construct: format!("type, whose F# spelling nests past {MAX_TYPE_DEPTH}"),
996 at: fsharp_type_name(ast, id),
997 });
998 }
999 let Ok(node) = ast.type_(id) else {
1000 return Err(MaterialiseError::Unevaluated {
1001 construct: "type behind a member".to_string(),
1002 at: at.to_string(),
1003 });
1004 };
1005 Ok(match &node.kind {
1006 TypeKind::Primitive(kind) => fsharp_primitive_type(*kind),
1007 TypeKind::String { .. } | TypeKind::Fixed { .. } => fsharp::Type::text(),
1008 TypeKind::Sequence { element, .. } => {
1009 let Some(element) = element.resolved() else {
1010 return Err(MaterialiseError::Unevaluated {
1011 construct: "sequence element".to_string(),
1012 at: node.name.clone(),
1013 });
1014 };
1015 fsharp::Type::array(value_type_at(ast, element, at, depth + 1)?)
1016 }
1017 TypeKind::Array { element, dims } => {
1018 let Some(element) = element.resolved() else {
1019 return Err(MaterialiseError::Unevaluated {
1020 construct: "array element".to_string(),
1021 at: node.name.clone(),
1022 });
1023 };
1024 let mut type_ = value_type_at(ast, element, at, depth + 1)?;
1025 for dim in dims.iter().rev() {
1026 let _ = ast
1027 .eval_integral(*dim)
1028 .map_err(|_| MaterialiseError::Unevaluated {
1029 construct: "array length".to_string(),
1030 at: node.name.clone(),
1031 })?;
1032 type_ = fsharp::Type::array(type_);
1033 }
1034 type_
1035 }
1036 TypeKind::Map { .. } => {
1037 fsharp::Type::array(fsharp::Type::named(&map_entry_name_at(ast, id, depth + 1)?))
1038 }
1039 TypeKind::Alias { aliased } if node.name.is_empty() => {
1040 let Some(target) = aliased.resolved() else {
1041 return Err(MaterialiseError::Unevaluated {
1042 construct: "inline typedef target".to_string(),
1043 at: at.to_string(),
1044 });
1045 };
1046 value_type_at(ast, target, at, depth + 1)?
1047 }
1048 _ => fsharp::Type::named(&fsharp_type_name(ast, id)),
1049 })
1050}
1051
1052#[must_use]
1060pub fn fsharp_primitive_type(kind: PrimitiveKind) -> fsharp::Type {
1061 use fsharp::Primitive as P;
1062 let primitive = match kind {
1063 PrimitiveKind::Boolean => P::Bool,
1064 PrimitiveKind::Octet | PrimitiveKind::UInt8 | PrimitiveKind::Char => P::Byte,
1065 PrimitiveKind::Int8 => P::SByte,
1066 PrimitiveKind::WChar | PrimitiveKind::UInt16 => P::UInt16,
1067 PrimitiveKind::Int16 => P::Int16,
1068 PrimitiveKind::Int32 => P::Int32,
1069 PrimitiveKind::UInt32 => P::UInt32,
1070 PrimitiveKind::Int64 => P::Int64,
1071 PrimitiveKind::UInt64 => P::UInt64,
1072 PrimitiveKind::Float32 => P::Float32,
1073 PrimitiveKind::Float64 => P::Double,
1074 PrimitiveKind::Float128 => return fsharp::Type::text(),
1075 };
1076 fsharp::Type::Primitive(primitive)
1077}
1078
1079#[must_use]
1083pub const fn fsharp_holder(bits: u16) -> fsharp::Primitive {
1084 match bits {
1085 0..=8 => fsharp::Primitive::Byte,
1086 9..=16 => fsharp::Primitive::UInt16,
1087 17..=32 => fsharp::Primitive::UInt32,
1088 _ => fsharp::Primitive::UInt64,
1089 }
1090}
1091
1092#[must_use]
1121pub fn typed_int(primitive: fsharp::Primitive, value: fsharp::Expr) -> fsharp::Expr {
1122 use fsharp::Primitive as P;
1123 let (conversion, suffix) = match primitive {
1124 P::SByte => ("sbyte", "y"),
1125 P::Byte => ("byte", "uy"),
1126 P::Int16 => ("int16", "s"),
1127 P::UInt16 => ("uint16", "us"),
1128 P::Int32 => return value,
1133 P::UInt32 => ("uint32", "u"),
1134 P::Int64 => ("int64", "L"),
1135 P::UInt64 => ("uint64", "UL"),
1136 P::Bool | P::Float32 | P::Double | P::String | P::Unit => return value,
1137 };
1138 if let fsharp::Expr::Literal(Literal::Int(digits)) = &value {
1139 return fsharp::Expr::Literal(Literal::Int(format!("{digits}{suffix}")));
1140 }
1141 fsharp::Expr::call(conversion, vec![value])
1142}
1143
1144#[must_use]
1149pub fn fsharp_type_name(ast: &Ast, id: Id<TypeNode>) -> String {
1150 let Ok(any) = ast.any_of_type(id) else {
1151 return "ZdUnnamed".to_string();
1152 };
1153 match ast.scoped_name_of(any) {
1154 Some(scoped) => flatten(scoped.segments()),
1155 None => "ZdUnnamed".to_string(),
1156 }
1157}
1158
1159pub fn fsharp_map_entry_name(ast: &Ast, id: Id<TypeNode>) -> Result<String, MaterialiseError> {
1168 map_entry_name_at(ast, id, 0)
1169}
1170
1171fn map_entry_name_at(
1186 ast: &Ast,
1187 id: Id<TypeNode>,
1188 depth: usize,
1189) -> Result<String, MaterialiseError> {
1190 if depth > MAX_TYPE_DEPTH {
1191 return Err(MaterialiseError::Unevaluated {
1192 construct: format!("map entry name, which nests past {MAX_TYPE_DEPTH}"),
1193 at: fsharp_type_name(ast, id),
1194 });
1195 }
1196 let Ok(node) = ast.type_(id) else {
1197 return Ok(format!("{INVENTED_PREFIX}unknown"));
1198 };
1199 let TypeKind::Map { key, value, .. } = &node.kind else {
1200 return Ok(format!("{INVENTED_PREFIX}unknown"));
1201 };
1202 let key = match key.resolved() {
1203 Some(k) => type_tag(ast, k, depth + 1)?,
1204 None => "unknown".to_string(),
1205 };
1206 let value = match value.resolved() {
1207 Some(v) => type_tag(ast, v, depth + 1)?,
1208 None => "unknown".to_string(),
1209 };
1210 Ok(format!("{INVENTED_PREFIX}{key}_{value}"))
1211}
1212
1213fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> Result<String, MaterialiseError> {
1221 if depth > MAX_TYPE_DEPTH {
1222 return Err(MaterialiseError::Unevaluated {
1223 construct: format!("type tag, which nests past {MAX_TYPE_DEPTH}"),
1224 at: fsharp_type_name(ast, id),
1225 });
1226 }
1227 Ok(match ast.type_(id).map(|node| node.kind.clone()) {
1228 Ok(TypeKind::Primitive(kind)) => kind.as_str().replace(' ', "_"),
1229 Ok(TypeKind::String {
1230 width: CharWidth::Char,
1231 ..
1232 }) => "string".to_string(),
1233 Ok(TypeKind::String {
1234 width: CharWidth::WChar,
1235 ..
1236 }) => "wstring".to_string(),
1237 Ok(TypeKind::Sequence { element, .. }) => match element.resolved() {
1238 Some(element) => format!("seq_of_{}", type_tag(ast, element, depth + 1)?),
1239 None => "seq_of_unknown".to_string(),
1240 },
1241 Ok(TypeKind::Array { element, dims }) => match element.resolved() {
1242 Some(element) => {
1243 let mut name = format!("arr_of_{}", type_tag(ast, element, depth + 1)?);
1244 for dim in &dims {
1245 match ast.eval_integral(*dim) {
1246 Ok(length) => name.push_str(&format!("_{length}")),
1247 Err(_) => name.push_str("_x"),
1248 }
1249 }
1250 name
1251 }
1252 None => "arr_of_unknown".to_string(),
1253 },
1254 Ok(TypeKind::Map { .. }) => map_entry_name_at(ast, id, depth + 1)?,
1255 _ => fsharp_type_name(ast, id),
1256 })
1257}
1258
1259fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String {
1261 let Ok(any) = ast.any_of_type(id) else {
1262 return String::new();
1263 };
1264 let Some(scoped) = ast.scoped_name_of(any) else {
1265 return String::new();
1266 };
1267 let segments = scoped.segments();
1268 if segments.len() <= 1 {
1269 return String::new();
1270 }
1271 let mut prefix = segments[..segments.len() - 1].join("_");
1272 prefix.push('_');
1273 prefix
1274}
1275
1276fn declared_name(ast: &Ast, name: &str, module: Id<zerodds_ast::ModuleNode>) -> String {
1278 let mut segments: Vec<String> = Vec::new();
1279 let mut current = Some(module);
1280 let mut depth = 0;
1281 while let Some(id) = current {
1282 if depth > MAX_MODULE_DEPTH {
1283 break;
1284 }
1285 depth += 1;
1286 let Ok(node) = ast.module(id) else { break };
1287 if node.name.is_empty() {
1288 break;
1289 }
1290 segments.push(node.name.clone());
1291 current = node.parent;
1292 }
1293 segments.reverse();
1294 segments.push(name.to_string());
1295 fsharp::escape_decl(&segments.join("_"))
1296}
1297
1298fn flatten(segments: &[String]) -> String {
1302 fsharp::escape_decl(&segments.join("_"))
1303}
1304
1305#[must_use]
1311pub fn field_name(name: &str) -> String {
1312 fsharp::escape_field(name)
1313}
1314
1315fn is_external(member: &zerodds_ast::MemberNode) -> bool {
1317 member
1318 .facets
1319 .xtypes
1320 .as_ref()
1321 .is_some_and(|xtypes| xtypes.is_external)
1322}
1323
1324fn member_field(
1332 ast: &Ast,
1333 member: &zerodds_ast::MemberNode,
1334 target: Id<TypeNode>,
1335) -> Result<fsharp::ClassField, MaterialiseError> {
1336 let spelled = field_name(&member.name);
1337 let mut doc = idl_note(&spelled, &member.name);
1338 let type_ = fsharp_member_type(ast, target, &member.name)?;
1339 if is_external(member) {
1340 doc.push("Stored by reference — @external, DDS-XTypes 1.3 Table 21. An F# class");
1341 doc.push("is a reference already, and the indirection is what makes a");
1342 doc.push("self-recursive type finite.");
1343 }
1344 let (type_, default) = if member.facets.is_optional() {
1345 doc.push("Optional. `None` when the member is absent — DDS-XTypes 1.3");
1346 doc.push("7.2.2.4.4.4.7. In a @mutable type an absent member is not written");
1347 doc.push("at all; outside one its absence is written.");
1348 (fsharp::Type::option(type_), fsharp::Expr::None_)
1349 } else {
1350 let default = default_fsharp::default_expr(ast, target)
1351 .map_err(|error| no_form(&error, &member.name))?;
1352 (type_, default)
1353 };
1354 Ok(fsharp::ClassField {
1355 doc,
1356 name: Ident::new(&spelled),
1357 type_,
1358 default,
1359 })
1360}
1361
1362fn idl_note(spelled: &str, declared: &str) -> Doc {
1364 if spelled == declared {
1365 return Doc::none();
1366 }
1367 Doc::line(&format!("IDL: `{declared}`."))
1368}
1369
1370fn with_idl_note(mut doc: Doc, spelled: &str, declared: &str) -> Doc {
1372 if spelled != declared {
1373 doc.push(&format!("IDL: `{declared}`."));
1374 }
1375 doc
1376}
1377
1378fn remember(
1383 emitted: &mut Vec<(String, String)>,
1384 spelled: &str,
1385 declared: &str,
1386) -> Result<(), MaterialiseError> {
1387 if let Some((_, first)) = emitted.iter().find(|(other, _)| other == spelled) {
1388 return Err(MaterialiseError::SpellingCollision {
1389 first: first.clone(),
1390 second: declared.to_string(),
1391 spelled: spelled.to_string(),
1392 language: LANGUAGE,
1393 at: "the generated file".to_string(),
1394 });
1395 }
1396 emitted.push((spelled.to_string(), declared.to_string()));
1397 Ok(())
1398}
1399
1400fn as_pairs(pairs: &[(String, String)]) -> Vec<(&str, &str)> {
1401 pairs
1402 .iter()
1403 .map(|(spelled, declared)| (spelled.as_str(), declared.as_str()))
1404 .collect()
1405}
1406
1407fn ghost_created_for(applied: &Applied, node: NodeId, type_name: &str) -> Option<GhostId> {
1409 let anchor = applied
1410 .origin
1411 .iter()
1412 .find(|(_, model)| **model == node)
1413 .map(|(ghost, _)| *ghost)?;
1414 let typ = applied.graph.types.lookup(type_name)?;
1415 applied
1416 .graph
1417 .nodes_of_type(typ)
1418 .map(|created| created.id)
1419 .find(|created| {
1420 applied.graph.connected(&anchor, created)
1421 || applied
1422 .graph
1423 .parts(&anchor)
1424 .any(|part| applied.graph.connected(&part.other, created))
1425 })
1426}
1427
1428fn fsharp_constant(
1430 value: &zerodds_ast::ConstValue,
1431 type_: &fsharp::Type,
1432 is_long_double: bool,
1433 at: &str,
1434) -> Result<fsharp::Expr, MaterialiseError> {
1435 use zerodds_ast::ConstValue;
1436 let refuse = |construct: &str| MaterialiseError::NoLanguageForm {
1437 construct: construct.to_string(),
1438 language: LANGUAGE,
1439 at: at.to_string(),
1440 };
1441 let is_text = matches!(type_, fsharp::Type::Primitive(fsharp::Primitive::String));
1442 let wrap = |value: fsharp::Expr| match type_ {
1448 fsharp::Type::Primitive(primitive) => typed_int(*primitive, value),
1449 _ => value,
1450 };
1451 Ok(match value {
1452 ConstValue::Boolean(flag) => fsharp::Expr::Literal(Literal::Bool(*flag)),
1453 ConstValue::Integer(number) => wrap(fsharp::Expr::int(*number)),
1454 ConstValue::Char(character) => wrap(fsharp::Expr::int(i128::from(u32::from(*character)))),
1455 ConstValue::WChar(point) => wrap(fsharp::Expr::int(i128::from(*point))),
1456 ConstValue::Float { mantissa, exponent } => {
1457 let decimal = constant::float_text(mantissa, exponent.as_deref());
1458 if is_text && !is_long_double {
1464 return Err(refuse("a floating-point constant of a text-shaped type"));
1465 }
1466 if is_long_double {
1467 return Ok(fsharp::Expr::Literal(Literal::Str(decimal)));
1468 }
1469 let literal = fsharp::Expr::Literal(Literal::Float(decimal));
1470 if *type_ == fsharp::Type::Primitive(fsharp::Primitive::Float32) {
1475 fsharp::Expr::call("float32", vec![literal])
1476 } else {
1477 literal
1478 }
1479 }
1480 ConstValue::Fixed { digits, scale } => {
1481 fsharp::Expr::Literal(Literal::Str(constant::fixed_decimal(digits, *scale)))
1482 }
1483 ConstValue::String(text) => fsharp::Expr::Literal(Literal::Str(text.clone())),
1484 ConstValue::WString(points) => fsharp::Expr::Literal(Literal::Str(
1485 constant::wstring_utf8(points)
1486 .ok_or_else(|| refuse("a `wstring` constant holding a lone surrogate"))?,
1487 )),
1488 _ => return Err(refuse("a constant form this binding has no literal for")),
1489 })
1490}