1use crate::adapter::constant;
237use crate::adapter::cst::{MaterialiseError, no_spelling_collision};
238use crate::adapter::interface;
239use crate::apply::Applied;
240use crate::fixed_literal::zero_decimal;
241use crate::verbatim;
242use seesaw_tgg::ident::GhostId;
243use zerodds_ast::{
244 Ast, Bound, CharWidth, ConstValue, ModuleNode, PrimitiveKind, TypeKind, TypeNode,
245};
246use zerodds_ast_core::{Id, NodeId};
247use zerodds_lang_cst::common::{Doc, Ident, Literal};
248use zerodds_lang_cst::lua::{self, Entry, Expr, File, Function, Place, Stat, Table};
249
250const LANGUAGE: &str = "Lua";
252
253pub const MODULE: &str = "M";
260
261pub const DISCRIMINATOR: &str = "_d";
267
268const DESCRIPTORS: &str = "_descriptors";
270
271const NAMES: &str = "_names";
273
274const RESOLVED: &str = "_resolved";
276
277const MAX_MODULE_DEPTH: usize = 64;
280
281const MAX_TYPE_DEPTH: usize = 64;
287
288pub fn lua_file(ast: &Ast, applied: &Applied) -> Result<File, MaterialiseError> {
301 let mut builder = Builder::new(ast);
302 builder.walk(applied)?;
303 Ok(File {
304 end_text: verbatim::file_slots(ast, verbatim::aliases::LUA).1,
305 doc: Doc::lines(&[
306 "Generated by ZeroDDS from an IDL source. Do not edit.",
307 "",
308 "Lua declares nothing. A struct, a sequence, an array, a map and a",
309 "union are all tables, and `math.type` is the only type question a",
310 "program can ask of a value. So every IDL fact is written into a",
311 "descriptor table instead: the member list in declaration order, and",
312 "for each member its IDL name, its Lua key, its kind, its width, its",
313 "signedness, its bound, whether it is optional, whether it is a key",
314 "and its member id.",
315 "",
316 "M._descriptors maps the IDL scoped name of every declared type to its",
317 "descriptor. M._names lists those names in declaration order.",
318 "",
319 "The ---@class and ---@field blocks are comments. lua-language-server",
320 "reads them; nothing in the Lua runtime does.",
321 "",
322 "A sequence, an array and a map are 1-based here and 0-based in IDL:",
323 "element i of a sequence is t[i + 1]. Every such descriptor says",
324 "base = 1 rather than leaving it to be assumed.",
325 ]),
326 body: {
327 let (begin_file, _) = verbatim::file_slots(ast, verbatim::aliases::LUA);
328 let mut body: Vec<Stat> = begin_file.into_iter().map(Stat::Verbatim).collect();
329 body.extend(builder.finish());
330 body
331 },
332 returns: Expr::place(Place::name(MODULE)),
333 })
334}
335
336pub fn lua_items(ast: &Ast, applied: &Applied) -> Result<Vec<Stat>, MaterialiseError> {
347 let mut builder = Builder::new(ast);
348 builder.walk(applied)?;
349 let mut out = builder.module_tables;
350 out.append(&mut builder.declarations);
351 Ok(verbatim::weave_file(ast, verbatim::aliases::LUA, out))
352}
353
354impl verbatim::Carrier for Stat {
358 fn verbatim(text: String) -> Self {
359 Self::Verbatim(text)
360 }
361
362 fn take_into_body(&mut self, _: &str, _: &mut Vec<String>, _: &mut Vec<String>) -> bool {
363 false
364 }
365}
366
367pub fn lua_type_descriptor(
379 ast: &Ast,
380 target: Id<TypeNode>,
381 at: &str,
382) -> Result<Table, MaterialiseError> {
383 let mut refs = Vec::new();
384 Ok(Table::of(type_entries(ast, target, at, &mut refs, 0)?))
385}
386
387struct Builder<'a> {
391 ast: &'a Ast,
392 module_tables: Vec<Stat>,
394 module_paths: Vec<String>,
396 declarations: Vec<Stat>,
398 emitted: Vec<(String, String)>,
400 names: Vec<String>,
402 refs: Vec<(String, String)>,
404 constructors: Vec<Place>,
406}
407
408impl<'a> Builder<'a> {
409 fn new(ast: &'a Ast) -> Self {
410 Self {
411 ast,
412 module_tables: Vec::new(),
413 module_paths: Vec::new(),
414 declarations: Vec::new(),
415 emitted: Vec::new(),
416 names: Vec::new(),
417 refs: Vec::new(),
418 constructors: Vec::new(),
419 }
420 }
421
422 fn walk(&mut self, applied: &Applied) -> Result<(), MaterialiseError> {
431 let ast = self.ast;
432 for (id, _) in ast.types() {
433 let start = self.declarations.len();
434 self.declare_type(applied, id)?;
435 verbatim::weave(
436 verbatim::declaration_slots(ast, id, verbatim::aliases::LUA),
437 "",
438 start,
439 &mut self.declarations,
440 );
441 }
442 self.declare_constants(applied)?;
443 self.declare_exceptions(applied)?;
444 self.declare_interfaces(applied)?;
445 Ok(())
446 }
447
448 fn finish(mut self) -> Vec<Stat> {
450 let mut out = vec![
451 Stat::Local {
452 doc: Doc::none(),
453 name: Ident::new(MODULE),
454 value: Some(Expr::Table(Table::default())),
455 },
456 Stat::Assign {
457 doc: Doc::lines(&[
458 "The descriptor of every type this module declares, by its IDL",
459 "scoped name. That name is the only one that survives every",
460 "mapping in this file, so it is what a codec looks a type up by.",
461 ]),
462 target: Place::name(MODULE).field(DESCRIPTORS),
463 value: Expr::Table(Table::default()),
464 },
465 ];
466 out.append(&mut self.module_tables);
467 out.append(&mut self.declarations);
468 out.push(Stat::Assign {
469 doc: Doc::lines(&[
470 "Every declared type, in IDL declaration order.",
471 "",
472 "The array part of a table, because that is the only part of a Lua",
473 "table that has an order: `next` does not specify one for the rest",
474 "(Lua 5.4 reference 6.1).",
475 ]),
476 target: Place::name(MODULE).field(NAMES),
477 value: Expr::Table(Table::of(
478 self.names
479 .iter()
480 .map(|name| Entry::positional(Expr::string(name)))
481 .collect(),
482 )),
483 });
484 out.push(Stat::Assign {
485 doc: Doc::lines(&[
486 "Every type a descriptor names, resolved while this chunk runs.",
487 "",
488 "Reading a missing key in Lua answers nil and carries on, so a",
489 "descriptor naming a type this module does not declare would be",
490 "found by whatever read it next rather than here. `assert` is what",
491 "notices, and it notices at load.",
492 ]),
493 target: Place::name(MODULE).field(RESOLVED),
494 value: Expr::Table(Table::of(self.reference_entries())),
495 });
496 out.push(Stat::Comment(Doc::lines(&[
497 "Every constructor, run once and discarded. A Lua function body is",
498 "not checked against anything until it is called, so a load that did",
499 "not call these would say nothing about them.",
500 ])));
501 out.push(Stat::Local {
502 doc: Doc::none(),
503 name: Ident::new("_"),
504 value: Some(Expr::Table(Table::of(
505 self.constructors
506 .iter()
507 .map(|target| {
508 Entry::positional(Expr::Call {
509 func: Box::new(target.clone()),
510 args: Vec::new(),
511 })
512 })
513 .collect(),
514 ))),
515 });
516 out
517 }
518
519 fn reference_entries(&self) -> Vec<Entry> {
521 self.refs
522 .iter()
523 .map(|(name, at)| Entry {
524 doc: Doc::none(),
525 key: lua::Key::Expr(Expr::string(name)),
526 value: Expr::Call {
527 func: Box::new(Place::name("assert")),
528 args: vec![
529 Expr::place(Place::Index {
530 base: Box::new(Place::name(MODULE).field(DESCRIPTORS)),
531 key: Box::new(Expr::string(name)),
532 }),
533 Expr::string(&format!(
534 "{at} names {name}, which this module does not declare"
535 )),
536 ],
537 },
538 })
539 .collect()
540 }
541
542 fn place_of(&mut self, module: Id<ModuleNode>, name: &str) -> Result<Place, MaterialiseError> {
552 let ast = self.ast;
553 let chain = module_chain(ast, module)?;
554 let mut place = Place::name(MODULE);
555 let mut text = MODULE.to_string();
556 for segment in &chain {
557 place = place.field(segment);
558 text.push('.');
559 text.push_str(segment);
560 if !self.module_paths.contains(&text) {
561 self.module_paths.push(text.clone());
562 self.module_tables.push(Stat::Assign {
563 doc: Doc::line(&format!(
564 "The IDL module `{segment}`, as the table its declarations nest in."
565 )),
566 target: place.clone(),
567 value: Expr::Table(Table::default()),
568 });
569 }
570 }
571 Ok(place.field(name))
572 }
573
574 fn path_text(module: &[String], name: &str) -> String {
576 let mut text = MODULE.to_string();
577 for segment in module {
578 text.push('.');
579 text.push_str(segment);
580 }
581 text.push('.');
582 text.push_str(name);
583 text
584 }
585
586 fn remember(&mut self, path: &str, declared: &str) -> Result<(), MaterialiseError> {
596 if let Some((_, first)) = self.emitted.iter().find(|(other, _)| other == path) {
597 return Err(MaterialiseError::SpellingCollision {
598 first: first.clone(),
599 second: declared.to_string(),
600 spelled: path.to_string(),
601 language: LANGUAGE,
602 at: "the generated chunk".to_string(),
603 });
604 }
605 self.emitted.push((path.to_string(), declared.to_string()));
606 Ok(())
607 }
608
609 fn emit(&mut self, decl: Decl) -> Result<(), MaterialiseError> {
612 let ast = self.ast;
613 let chain = module_chain(ast, decl.module)?;
614 let path = Self::path_text(&chain, &decl.name);
615 self.remember(&path, &decl.scoped)?;
616 let target = self.place_of(decl.module, &decl.name)?;
617
618 if !decl.annotation.is_empty() {
619 self.declarations.push(Stat::Annotation(decl.annotation));
620 }
621 self.declarations.push(Stat::Assign {
622 doc: decl.doc,
623 target: target.clone(),
624 value: Expr::Table(decl.descriptor),
625 });
626 self.declarations.push(Stat::Assign {
627 doc: Doc::none(),
628 target: Place::Index {
629 base: Box::new(Place::name(MODULE).field(DESCRIPTORS)),
630 key: Box::new(Expr::string(&decl.scoped)),
631 },
632 value: Expr::place(target.clone()),
633 });
634 self.names.push(decl.scoped.clone());
635
636 if let Some(value) = decl.constructor {
637 let new = target.field("new");
638 let mut doc = Doc::line(&format!(
639 "A `{}` with every member at the default of its type.",
640 decl.scoped
641 ));
642 doc.push("");
643 doc.push("An @optional member is not given a key at all: in Lua the absence");
644 doc.push("of a value is the absence of the key (DDS-XTypes 1.3 7.2.2.4.4.4.7).");
645 doc.push(&format!("@return {}", decl.class));
646 self.declarations.push(Stat::Function(Function {
647 doc,
648 target: new.clone(),
649 params: Vec::new(),
650 body: vec![Stat::Return(Some(value))],
651 }));
652 self.constructors.push(new);
653 }
654 Ok(())
655 }
656}
657
658struct Decl {
660 module: Id<ModuleNode>,
662 name: String,
664 scoped: String,
666 class: String,
668 annotation: Doc,
670 doc: Doc,
672 descriptor: Table,
674 constructor: Option<Expr>,
676}
677
678impl Builder<'_> {
681 #[allow(clippy::too_many_lines)]
682 fn declare_type(
683 &mut self,
684 applied: &Applied,
685 id: Id<TypeNode>,
686 ) -> Result<(), MaterialiseError> {
687 let ast = self.ast;
688 let Ok(node) = ast.type_(id) else {
689 return Ok(());
690 };
691 let scoped = scoped_name(ast, id);
692 let class = class_name(ast, id);
693
694 match &node.kind {
695 TypeKind::Primitive(_)
701 | TypeKind::String { .. }
702 | TypeKind::Fixed { .. }
703 | TypeKind::Sequence { .. }
704 | TypeKind::Map { .. }
705 | TypeKind::Array { .. } => {}
706
707 TypeKind::Alias { aliased } => {
708 if node.name.is_empty() {
711 return Ok(());
712 }
713 if ghost_created_for(applied, node.node_id, "LuaAlias").is_none() {
714 return Ok(());
715 }
716 let Some(target) = aliased.resolved() else {
717 return Err(MaterialiseError::Unevaluated {
718 construct: "typedef target".to_string(),
719 at: node.name.clone(),
720 });
721 };
722 let inner = Table::of(type_entries(ast, target, &scoped, &mut self.refs, 0)?);
723 self.emit(Decl {
724 module: node.module,
725 name: node.name.clone(),
726 scoped: scoped.clone(),
727 class: cats_type(ast, target, 0),
728 annotation: Doc::none(),
729 doc: Doc::lines(&[
730 "A typedef. Lua has no alias to declare, so what is left of it",
731 "is this entry: the name, and the descriptor of what it names.",
732 "Every use of the alias takes the target's descriptor.",
733 ]),
734 descriptor: Table::of(vec![
735 Entry::named("idl", Expr::string(&scoped)),
736 Entry::named("name", Expr::string(&node.name)),
737 Entry::named("kind", Expr::string("alias")),
738 Entry::named("target", Expr::Table(inner)),
739 ]),
740 constructor: Some(default_expr(ast, target, &scoped, 0)?),
741 })?;
742 }
743
744 TypeKind::Enumeration { enumerators } => {
745 if ghost_created_for(applied, node.node_id, "LuaEnum").is_none() {
746 return Ok(());
747 }
748 let mut ordered = Vec::new();
749 let mut values = Vec::new();
750 let mut names = Vec::new();
751 let mut first = None;
752 for enumerator in enumerators {
753 let Ok(enum_node) = ast.enumerator(*enumerator) else {
754 continue;
755 };
756 if ghost_created_for(applied, enum_node.node_id, "LuaEnumerator").is_none() {
757 continue;
758 }
759 let value = self
760 .ast
761 .effective_enumerator_value(*enumerator)
762 .ok_or_else(|| MaterialiseError::Unevaluated {
763 construct: "enumerator value".to_string(),
764 at: enum_node.name.clone(),
765 })?;
766 let literal = integer_literal(value, &enum_node.name)?;
767 if first.is_none() {
768 first = Some(literal.clone());
769 }
770 ordered.push(Entry::positional(Expr::Table(Table::of(vec![
771 Entry::named("name", Expr::string(&enum_node.name)),
772 Entry::named("value", literal.clone()),
773 ]))));
774 values.push(Entry::named(&enum_node.name, literal.clone()));
775 names.push(Entry {
776 doc: Doc::none(),
777 key: lua::Key::Expr(literal),
778 value: Expr::string(&enum_node.name),
779 });
780 }
781 self.emit(Decl {
782 module: node.module,
783 name: node.name.clone(),
784 scoped: scoped.clone(),
785 class: class.clone(),
786 annotation: Doc::none(),
787 doc: Doc::lines(&[
788 "An enumeration, as the two maps and the ordered list an",
789 "enumerator carries. The values are written out because @value",
790 "admits gaps, so a position is not a value.",
791 "",
792 "`names` is the inverse of `values`; a Lua table holds both",
793 "directions at once because its keys may be integers as well as",
794 "strings.",
795 ]),
796 descriptor: Table::of(vec![
797 Entry::named("idl", Expr::string(&scoped)),
798 Entry::named("name", Expr::string(&node.name)),
799 Entry::named("kind", Expr::string("enum")),
800 Entry::named(
801 "bit_bound",
802 Expr::int(i128::from(ast.effective_bit_bound(id))),
803 ),
804 Entry::named("enumerators", Expr::Table(Table::of(ordered))),
805 Entry::named("values", Expr::Table(Table::of(values))),
806 Entry::named("names", Expr::Table(Table::of(names))),
807 ]),
808 constructor: first,
813 })?;
814 }
815
816 TypeKind::Bitmask { bits } => {
817 if ghost_created_for(applied, node.node_id, "LuaBitmask").is_none() {
818 return Ok(());
819 }
820 let bound = ast.effective_bit_bound(id);
821 let mut ordered = Vec::new();
822 let mut values = Vec::new();
823 for bit in bits {
824 let Ok(bit_node) = ast.bit_value(*bit) else {
825 continue;
826 };
827 if ghost_created_for(applied, bit_node.node_id, "LuaBitEntry").is_none() {
828 continue;
829 }
830 let position = ast.effective_bit_position(*bit).ok_or_else(|| {
831 MaterialiseError::Unevaluated {
832 construct: "bit position".to_string(),
833 at: bit_node.name.clone(),
834 }
835 })?;
836 let shift = Expr::Binary {
840 op: "<<",
841 lhs: Box::new(Expr::int(1)),
842 rhs: Box::new(Expr::int(i128::from(position))),
843 };
844 ordered.push(Entry::positional(Expr::Table(Table::of(vec![
845 Entry::named("name", Expr::string(&bit_node.name)),
846 Entry::named("position", Expr::int(i128::from(position))),
847 Entry::named("value", shift.clone()),
848 ]))));
849 values.push(Entry::named(&bit_node.name, shift));
850 }
851 self.emit(Decl {
852 module: node.module,
853 name: node.name.clone(),
854 scoped: scoped.clone(),
855 class: class.clone(),
856 annotation: Doc::none(),
857 doc: Doc::lines(&[
858 "A bitmask: one value per named bit, and the holder width the",
859 "@bit_bound gives (Table 33 of DDS-XTypes 1.3 7.4.1.1.3.2).",
860 "",
861 "A Lua integer is 64-bit two's complement, so a bit_bound of 64",
862 "puts bit 63 in the sign; the width is here so a codec masks",
863 "rather than assumes.",
864 ]),
865 descriptor: Table::of(vec![
866 Entry::named("idl", Expr::string(&scoped)),
867 Entry::named("name", Expr::string(&node.name)),
868 Entry::named("kind", Expr::string("bitmask")),
869 Entry::named("bit_bound", Expr::int(i128::from(bound))),
870 Entry::named("bits", Expr::Table(Table::of(ordered))),
871 Entry::named("values", Expr::Table(Table::of(values))),
872 ]),
873 constructor: Some(Expr::int(0)),
875 })?;
876 }
877
878 TypeKind::Bitset { .. } => {
879 if ghost_created_for(applied, node.node_id, "LuaBitset").is_none() {
880 return Ok(());
881 }
882 let mut members = Vec::new();
883 let mut defaults = Vec::new();
884 let mut fields = Vec::new();
885 let mut pairs = Vec::new();
886 for field in &ast.all_bitfields_of(id) {
889 let Ok(field_node) = ast.bitfield(*field) else {
890 continue;
891 };
892 let Some(name) = field_node.name.as_ref() else {
895 continue;
896 };
897 pairs.push((name.clone(), name.clone()));
898 members.push(Entry::positional(Expr::Table(Table::of(vec![
899 Entry::named("name", Expr::string(name)),
900 Entry::named("key", Expr::string(name)),
901 Entry::named("lua", Expr::string("integer")),
902 Entry::named("kind", Expr::string("int")),
903 Entry::named("bits", Expr::int(i128::from(field_node.bits))),
904 Entry::named("signed", Expr::bool(false)),
905 Entry::named("wraps", Expr::bool(false)),
906 Entry::named("optional", Expr::bool(false)),
907 Entry::named("is_key", Expr::bool(false)),
908 Entry::named("id", Expr::bool(false)),
909 ]))));
910 defaults.push(Entry::named(name, Expr::int(0)));
911 fields.push(cats_field(name, "integer", false));
912 }
913 no_spelling_collision(as_pairs(&pairs), LANGUAGE, &node.name)?;
914 self.emit(Decl {
915 module: node.module,
916 name: node.name.clone(),
917 scoped: scoped.clone(),
918 class: class.clone(),
919 annotation: cats_class(&class, &fields),
920 doc: Doc::lines(&[
921 "A bitset. Each field states its own bit width; Lua has no",
922 "fixed-width integer, so nothing is rounded up to a holder the",
923 "way the C and Go columns have to.",
924 "",
925 "The packing into the anchor is the codec's, because the order",
926 "on the wire is the declaration's.",
927 ]),
928 descriptor: Table::of(vec![
929 Entry::named("idl", Expr::string(&scoped)),
930 Entry::named("name", Expr::string(&node.name)),
931 Entry::named("kind", Expr::string("bitset")),
932 Entry::named("members", Expr::Table(Table::of(members))),
933 ]),
934 constructor: Some(Expr::Table(Table::of(defaults))),
935 })?;
936 }
937
938 TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
939 let is_value = matches!(node.kind, TypeKind::Valuetype { .. });
940 let created = if is_value { "LuaValue" } else { "LuaStruct" };
941 if ghost_created_for(applied, node.node_id, created).is_none() {
942 return Ok(());
943 }
944 let members = ast.members_of(id).to_vec();
947 let built =
948 self.aggregate(crate::adapter::c::struct_base(ast, id), &members, &scoped)?;
949 self.emit(Decl {
950 module: node.module,
951 name: node.name.clone(),
952 scoped: scoped.clone(),
953 class: class.clone(),
954 annotation: cats_class(&class, &built.fields),
955 doc: Doc::lines(&[
956 "The member list is the array part of `members`, so the index is",
957 "the declaration order — which is the order the wire uses",
958 "(DDS-XTypes 1.3 7.4.1.1.5.1). A Lua table has no order of its",
959 "own: `next` does not specify one (Lua 5.4 reference 6.1).",
960 ]),
961 descriptor: Table::of(vec![
962 Entry::named("idl", Expr::string(&scoped)),
963 Entry::named("name", Expr::string(&node.name)),
964 Entry::named(
965 "kind",
966 Expr::string(if is_value { "valuetype" } else { "struct" }),
967 ),
968 Entry::named(
969 "extensibility",
970 Expr::string(ast.effective_extensibility(id).as_str()),
971 ),
972 Entry::named("members", Expr::Table(Table::of(built.members))),
973 ]),
974 constructor: Some(Expr::Table(Table::of(built.defaults))),
975 })?;
976 }
977
978 TypeKind::Union { discriminator, .. } => {
979 if ghost_created_for(applied, node.node_id, "LuaUnion").is_none() {
980 return Ok(());
981 }
982 let Some(disc) = discriminator.resolved() else {
983 return Err(MaterialiseError::Unevaluated {
984 construct: "union discriminator".to_string(),
985 at: scoped,
986 });
987 };
988 let cases = ast.cases_of(id).to_vec();
989 let mut branches = Vec::new();
990 let mut pairs = vec![(DISCRIMINATOR.to_string(), "<discriminator>".to_string())];
991 for case in &cases {
992 let Ok(case_node) = ast.case(*case) else {
993 continue;
994 };
995 if ghost_created_for(applied, case_node.node_id, "LuaCase").is_none() {
996 continue;
997 }
998 let Ok(member) = ast.member(case_node.member) else {
999 continue;
1000 };
1001 pairs.push((member.name.clone(), member.name.clone()));
1002 branches.push(case_node.member);
1003 }
1004 no_spelling_collision(as_pairs(&pairs), LANGUAGE, &node.name)?;
1005
1006 let disc_entries = {
1007 let mut entries = vec![
1008 Entry::named("name", Expr::string(DISCRIMINATOR)),
1009 Entry::named("key", Expr::string(DISCRIMINATOR)),
1010 ];
1011 entries.append(&mut type_entries(
1012 ast,
1013 disc,
1014 &format!("{scoped}._d"),
1015 &mut self.refs,
1016 0,
1017 )?);
1018 entries
1019 };
1020 let built = self.aggregate(None, &branches, &scoped)?;
1021 let mut fields = vec![cats_field(DISCRIMINATOR, &cats_type(ast, disc, 0), false)];
1022 for field in &built.fields {
1026 fields.push(optional_field(field));
1027 }
1028 self.emit(Decl {
1029 module: node.module,
1030 name: node.name.clone(),
1031 scoped: scoped.clone(),
1032 class: class.clone(),
1033 annotation: cats_class(&class, &fields),
1034 doc: Doc::lines(&[
1035 "A union: the discriminator under `_d`, and one key per branch.",
1036 "",
1037 "Not a tag and a payload. An IDL discriminator may hold a value",
1038 "no branch selects (IDL 4.2 7.4.1.4.4.1), and only the selected",
1039 "branch is written (DDS-XTypes 1.3 7.4.1.1.5.2) — so `_d` is a",
1040 "member in its own right and the branches sit beside it, at most",
1041 "one present.",
1042 ]),
1043 descriptor: Table::of(vec![
1044 Entry::named("idl", Expr::string(&scoped)),
1045 Entry::named("name", Expr::string(&node.name)),
1046 Entry::named("kind", Expr::string("union")),
1047 Entry::named(
1048 "extensibility",
1049 Expr::string(ast.effective_extensibility(id).as_str()),
1050 ),
1051 Entry::named("discriminator", Expr::Table(Table::of(disc_entries))),
1052 Entry::named("members", Expr::Table(Table::of(built.members))),
1053 ]),
1054 constructor: Some(Expr::Table(Table::of(vec![Entry::named(
1058 DISCRIMINATOR,
1059 default_expr(ast, disc, &scoped, 0)?,
1060 )]))),
1061 })?;
1062 }
1063
1064 TypeKind::Native => {
1065 if ghost_created_for(applied, node.node_id, "LuaNative").is_none() {
1066 return Ok(());
1067 }
1068 let mapping = self
1072 .ast
1073 .any_of_type(id)
1074 .ok()
1075 .and_then(|any| ast.facets(any))
1076 .and_then(|facets| facets.corba.as_ref())
1077 .and_then(|corba| corba.native_mapping.clone());
1078 self.emit(Decl {
1079 module: node.module,
1080 name: node.name.clone(),
1081 scoped: scoped.clone(),
1082 class: class.clone(),
1083 annotation: Doc::none(),
1084 doc: Doc::lines(&[
1085 "A native type: opaque unless a facet named its mapping.",
1086 "",
1087 "`mapping = false` and not nil. A table entry set to nil is an",
1088 "entry that does not exist, so nil could not be told from a key",
1089 "this file never wrote.",
1090 ]),
1091 descriptor: Table::of(vec![
1092 Entry::named("idl", Expr::string(&scoped)),
1093 Entry::named("name", Expr::string(&node.name)),
1094 Entry::named("kind", Expr::string("native")),
1095 Entry::named(
1096 "mapping",
1097 mapping.as_deref().map_or(Expr::bool(false), Expr::string),
1098 ),
1099 ]),
1100 constructor: None,
1101 })?;
1102 }
1103
1104 TypeKind::ValueBox { .. } => {}
1108
1109 TypeKind::ObjectRef { .. }
1117 | TypeKind::AnnotationType { .. }
1118 | TypeKind::Any
1119 | TypeKind::ValueBase => {}
1120 }
1121 Ok(())
1122 }
1123
1124 fn aggregate(
1127 &mut self,
1128 base: Option<Id<TypeNode>>,
1129 members: &[Id<zerodds_ast::MemberNode>],
1130 at: &str,
1131 ) -> Result<Aggregate, MaterialiseError> {
1132 let ast = self.ast;
1133 let mut pairs = Vec::new();
1134 if base.is_some() {
1135 pairs.push(("parent".to_string(), "parent".to_string()));
1136 }
1137 for member in members {
1138 if let Ok(node) = ast.member(*member) {
1139 pairs.push((node.name.clone(), node.name.clone()));
1140 }
1141 }
1142 no_spelling_collision(as_pairs(&pairs), LANGUAGE, at)?;
1143
1144 let mut built = Aggregate::default();
1145 if let Some(base) = base {
1147 let site = format!("{at}.parent");
1148 let mut entries = vec![
1149 Entry::named("name", Expr::string("parent")),
1150 Entry::named("key", Expr::string("parent")),
1151 ];
1152 entries.append(&mut type_entries(ast, base, &site, &mut self.refs, 0)?);
1153 entries.push(Entry::named("optional", Expr::bool(false)));
1154 entries.push(Entry::named("is_key", Expr::bool(false)));
1155 entries.push(Entry::named("id", Expr::bool(false)));
1156 built
1157 .members
1158 .push(Entry::positional(Expr::Table(Table::of(entries))));
1159 built
1160 .fields
1161 .push(cats_field("parent", &cats_type(ast, base, 0), false));
1162 built.defaults.push(Entry {
1163 doc: Doc::none(),
1164 key: lua::table_key("parent"),
1165 value: default_expr(ast, base, &site, 0)?,
1166 });
1167 }
1168 for member in members {
1169 let Ok(node) = ast.member(*member) else {
1170 continue;
1171 };
1172 let site = format!("{at}.{}", node.name);
1177 let Some(target) = node.type_.resolved() else {
1178 return Err(MaterialiseError::Unevaluated {
1179 construct: "member type".to_string(),
1180 at: site,
1181 });
1182 };
1183 let optional = node.facets.is_optional();
1184 let mut entries = vec![
1185 Entry::named("name", Expr::string(&node.name)),
1186 Entry::named("key", Expr::string(&node.name)),
1187 ];
1188 entries.append(&mut type_entries(ast, target, &site, &mut self.refs, 0)?);
1189 entries.push(Entry::named("optional", Expr::bool(optional)));
1190 entries.push(Entry::named("is_key", Expr::bool(node.facets.is_key())));
1191 entries.push(Entry::named(
1196 "id",
1197 node.facets
1198 .effective_member_id()
1199 .map_or(Expr::bool(false), |id| Expr::int(i128::from(id))),
1200 ));
1201 built
1202 .members
1203 .push(Entry::positional(Expr::Table(Table::of(entries))));
1204 built
1205 .fields
1206 .push(cats_field(&node.name, &cats_type(ast, target, 0), optional));
1207 if !optional {
1212 built.defaults.push(Entry {
1213 doc: Doc::none(),
1214 key: lua::table_key(&node.name),
1215 value: default_expr(ast, target, &site, 0)?,
1216 });
1217 }
1218 }
1219 Ok(built)
1220 }
1221}
1222
1223#[derive(Default)]
1226struct Aggregate {
1227 members: Vec<Entry>,
1229 defaults: Vec<Entry>,
1231 fields: Vec<String>,
1233}
1234
1235impl Builder<'_> {
1238 fn declare_constants(&mut self, applied: &Applied) -> Result<(), MaterialiseError> {
1239 let ast = self.ast;
1240 for (_, node) in ast.constants() {
1241 if ghost_created_for(applied, node.node_id, "LuaConst").is_none() {
1242 continue;
1243 }
1244 let value = ast
1249 .eval_const(node.expr)
1250 .map_err(|_| MaterialiseError::Unevaluated {
1251 construct: "constant's value".to_string(),
1252 at: node.name.clone(),
1253 })?;
1254 let declared = node.type_.resolved();
1255 let spelling =
1256 declared.map_or_else(|| "long".to_string(), |target| idl_spelling(ast, target, 0));
1257 let chain = module_chain(ast, node.module)?;
1258 let path = Self::path_text(&chain, &node.name);
1259 let scoped = declared_scoped(&chain, &node.name);
1260 self.remember(&path, &scoped)?;
1261 let target = self.place_of(node.module, &node.name)?;
1262 let expr = constant_expr(
1263 &value,
1264 declared.is_some_and(|id| is_decimal_holder(ast, id)),
1265 &node.name,
1266 )?;
1267 self.declarations.push(Stat::Assign {
1268 doc: Doc::line(&format!("`const {spelling} {}` — IDL {scoped}.", node.name)),
1269 target,
1270 value: expr,
1271 });
1272 }
1273 Ok(())
1274 }
1275
1276 fn declare_exceptions(&mut self, applied: &Applied) -> Result<(), MaterialiseError> {
1277 let ast = self.ast;
1278 for (_, node) in ast.exceptions() {
1279 if ghost_created_for(applied, node.node_id, "LuaError").is_none() {
1280 continue;
1281 }
1282 let chain = module_chain(ast, node.module)?;
1283 let scoped = declared_scoped(&chain, &node.name);
1284 let class = dotted(&chain, &node.name);
1285 let built = self.aggregate(None, &node.members, &scoped)?;
1286 self.emit(Decl {
1287 module: node.module,
1288 name: node.name.clone(),
1289 scoped: scoped.clone(),
1290 class: class.clone(),
1291 annotation: cats_class(&class, &built.fields),
1292 doc: Doc::lines(&[
1293 "An exception, as the structure its members make. Raising is the",
1294 "RPC arc's decision; the shape is the IDL's.",
1295 ]),
1296 descriptor: Table::of(vec![
1297 Entry::named("idl", Expr::string(&scoped)),
1298 Entry::named("name", Expr::string(&node.name)),
1299 Entry::named("kind", Expr::string("exception")),
1300 Entry::named("members", Expr::Table(Table::of(built.members))),
1301 ]),
1302 constructor: Some(Expr::Table(Table::of(built.defaults))),
1303 })?;
1304 }
1305 Ok(())
1306 }
1307
1308 fn declare_interfaces(&mut self, applied: &Applied) -> Result<(), MaterialiseError> {
1309 let ast = self.ast;
1310 for (id, node) in ast.interfaces() {
1311 if ghost_created_for(applied, node.node_id, "LuaInterface").is_none() {
1312 continue;
1313 }
1314 let chain = module_chain(ast, node.module)?;
1315 let scoped = declared_scoped(&chain, &node.name);
1316 let class = dotted(&chain, &node.name);
1317 let mut operations = Vec::new();
1318 let mut signatures = Vec::new();
1319 let all = interface::all_operations_flattened(ast, id)
1332 .map_err(|error| interface::interface_error_to_materialise(error, &node.name))?;
1333 for op in all
1334 .iter()
1335 .filter(|op| op.origin == interface::Origin::Declared)
1336 {
1337 signatures.push((op.name.clone(), op.name.clone()));
1338 let mut params = Vec::new();
1339 for param in &op.params {
1340 let mut entries = vec![
1341 Entry::named("name", Expr::string(¶m.name)),
1342 Entry::named("key", Expr::string(¶m.name)),
1343 ];
1344 entries.append(&mut type_entries(
1345 ast,
1346 param.type_,
1347 &format!("{scoped}.{}({})", op.name, param.name),
1348 &mut self.refs,
1349 0,
1350 )?);
1351 params.push(Entry::positional(Expr::Table(Table::of(entries))));
1352 }
1353 let mut results = Vec::new();
1357 if let Some(returns) = op.returns {
1358 results.push(Entry::positional(Expr::Table(Table::of(type_entries(
1359 ast,
1360 returns,
1361 &format!("{scoped}.{}", op.name),
1362 &mut self.refs,
1363 0,
1364 )?))));
1365 }
1366 operations.push(Entry::positional(Expr::Table(Table::of(vec![
1367 Entry::named("name", Expr::string(&op.name)),
1368 Entry::named("key", Expr::string(&op.name)),
1369 Entry::named("params", Expr::Table(Table::of(params))),
1370 Entry::named("results", Expr::Table(Table::of(results))),
1371 Entry::named("raises", raise_names(&op.raises)),
1372 ]))));
1373 }
1374 let mut attributes = Vec::new();
1375 let mut attribute_signatures = Vec::new();
1376 let all_attrs = interface::all_attributes_flattened(ast, id)
1377 .map_err(|error| interface::interface_error_to_materialise(error, &node.name))?;
1378 for attribute in &all_attrs {
1379 attribute_signatures.push((attribute.name.clone(), attribute.name.clone()));
1380 let type_entries_for_attr = type_entries(
1381 ast,
1382 attribute.type_,
1383 &format!("{scoped}.{}", attribute.name),
1384 &mut self.refs,
1385 0,
1386 )?;
1387 attributes.push(Entry::positional(Expr::Table(Table::of(vec![
1388 Entry::named("name", Expr::string(&attribute.name)),
1389 Entry::named("key", Expr::string(&attribute.name)),
1390 Entry::named("type", Expr::Table(Table::of(type_entries_for_attr))),
1391 Entry::named("readonly", Expr::bool(attribute.readonly)),
1392 Entry::named("get_raises", raise_names(&attribute.get_raises)),
1393 Entry::named("set_raises", raise_names(&attribute.set_raises)),
1394 ]))));
1395 }
1396 no_spelling_collision(as_pairs(&signatures), LANGUAGE, &node.name)?;
1397 no_spelling_collision(as_pairs(&attribute_signatures), LANGUAGE, &node.name)?;
1398 self.emit(Decl {
1399 module: node.module,
1400 name: node.name.clone(),
1401 scoped: scoped.clone(),
1402 class,
1403 annotation: Doc::none(),
1404 doc: Doc::lines(&[
1405 "An interface, as the operations and attributes it has — its",
1406 "bases' before its own, because the table has no link to a base's.",
1407 "Emitted rather than refused: an operation's parameters and result",
1408 "are a shape, and a shape is exactly what this column carries — an",
1409 "attribute's type and `readonly` flag are the same kind of shape.",
1410 "What none of it is is a call — the transport is the RPC arc's",
1411 "business.",
1412 "",
1413 "`results` is an array of nought or one. A `void` operation and one",
1414 "whose result key was never written would otherwise be the same",
1415 "table, because a Lua key set to nil does not exist. `raises`/",
1416 "`get_raises`/`set_raises` are arrays of exception names, empty",
1417 "where the IDL declares none.",
1418 ]),
1419 descriptor: Table::of(vec![
1420 Entry::named("idl", Expr::string(&scoped)),
1421 Entry::named("name", Expr::string(&node.name)),
1422 Entry::named("kind", Expr::string("interface")),
1423 Entry::named("operations", Expr::Table(Table::of(operations))),
1424 Entry::named("attributes", Expr::Table(Table::of(attributes))),
1425 ]),
1426 constructor: None,
1427 })?;
1428 }
1429 Ok(())
1430 }
1431}
1432
1433fn raise_names(raises: &[interface::Raise]) -> Expr {
1444 Expr::Table(Table::of(
1445 raises
1446 .iter()
1447 .map(|raise| Entry::positional(Expr::string(&raise.name)))
1448 .collect(),
1449 ))
1450}
1451
1452#[allow(clippy::too_many_lines)]
1456fn type_entries(
1457 ast: &Ast,
1458 id: Id<TypeNode>,
1459 at: &str,
1460 refs: &mut Vec<(String, String)>,
1461 depth: usize,
1462) -> Result<Vec<Entry>, MaterialiseError> {
1463 if depth > MAX_TYPE_DEPTH {
1464 return Err(MaterialiseError::Unevaluated {
1465 construct: format!("type, whose descriptor nests past {MAX_TYPE_DEPTH}"),
1466 at: at.to_string(),
1467 });
1468 }
1469 let node = ast.type_(id).map_err(|_| MaterialiseError::Unevaluated {
1470 construct: "type".to_string(),
1471 at: at.to_string(),
1472 })?;
1473 let refused = match &node.kind {
1474 TypeKind::Any => Some("any"),
1475 TypeKind::ValueBase => Some("ValueBase"),
1476 TypeKind::ObjectRef { .. } => Some("Object"),
1477 TypeKind::AnnotationType { .. } => Some("annotation type"),
1478 _ => None,
1479 };
1480 if let Some(construct) = refused {
1481 return Err(MaterialiseError::NoLanguageForm {
1482 construct: construct.to_string(),
1483 language: LANGUAGE,
1484 at: at.to_string(),
1485 });
1486 }
1487 Ok(match &node.kind {
1488 TypeKind::Primitive(kind) => primitive_entries(*kind),
1489 TypeKind::String { width, bound } => vec![
1490 Entry::named("lua", Expr::string("string")),
1491 Entry::named("kind", Expr::string("string")),
1492 Entry::named("bits", Expr::int(char_bits(*width))),
1493 Entry::named("bound", Expr::int(bound_of(ast, *bound, at)?)),
1494 ],
1495 TypeKind::Fixed { digits, scale } => vec![
1496 Entry::named("lua", Expr::string("string")),
1497 Entry::named("kind", Expr::string("decimal")),
1498 Entry::named("digits", Expr::int(i128::from(*digits))),
1499 Entry::named("scale", Expr::int(i128::from(*scale))),
1500 ],
1501 TypeKind::Sequence { element, bound } => {
1502 let Some(element) = element.resolved() else {
1503 return Err(MaterialiseError::Unevaluated {
1504 construct: "sequence element".to_string(),
1505 at: at.to_string(),
1506 });
1507 };
1508 vec![
1509 Entry::named("lua", Expr::string("table")),
1510 Entry::named("kind", Expr::string("sequence")),
1511 Entry::named("base", Expr::int(1)),
1514 Entry::named("bound", Expr::int(bound_of(ast, *bound, at)?)),
1515 Entry::named(
1516 "element",
1517 Expr::Table(Table::of(type_entries(ast, element, at, refs, depth + 1)?)),
1518 ),
1519 ]
1520 }
1521 TypeKind::Array { element, dims } => {
1522 let Some(element) = element.resolved() else {
1523 return Err(MaterialiseError::Unevaluated {
1524 construct: "array element".to_string(),
1525 at: at.to_string(),
1526 });
1527 };
1528 let mut lengths = Vec::new();
1529 for dim in dims {
1530 let length =
1531 ast.eval_integral(*dim)
1532 .map_err(|_| MaterialiseError::Unevaluated {
1533 construct: "array length".to_string(),
1534 at: at.to_string(),
1535 })?;
1536 lengths.push(Entry::positional(Expr::int(length)));
1537 }
1538 vec![
1539 Entry::named("lua", Expr::string("table")),
1540 Entry::named("kind", Expr::string("array")),
1541 Entry::named("base", Expr::int(1)),
1542 Entry::named("dims", Expr::Table(Table::of(lengths))),
1546 Entry::named(
1547 "element",
1548 Expr::Table(Table::of(type_entries(ast, element, at, refs, depth + 1)?)),
1549 ),
1550 ]
1551 }
1552 TypeKind::Map { key, value, bound } => {
1553 let (Some(key), Some(value)) = (key.resolved(), value.resolved()) else {
1554 return Err(MaterialiseError::Unevaluated {
1555 construct: "map key or value".to_string(),
1556 at: at.to_string(),
1557 });
1558 };
1559 vec![
1560 Entry::named("lua", Expr::string("table")),
1561 Entry::named("kind", Expr::string("map")),
1562 Entry::named("base", Expr::int(1)),
1563 Entry::named("bound", Expr::int(bound_of(ast, *bound, at)?)),
1564 Entry::named(
1565 "key_type",
1566 Expr::Table(Table::of(type_entries(ast, key, at, refs, depth + 1)?)),
1567 ),
1568 Entry::named(
1569 "value_type",
1570 Expr::Table(Table::of(type_entries(ast, value, at, refs, depth + 1)?)),
1571 ),
1572 ]
1573 }
1574 TypeKind::Alias { aliased } => {
1578 let Some(target) = aliased.resolved() else {
1579 return Err(MaterialiseError::Unevaluated {
1580 construct: "typedef target".to_string(),
1581 at: at.to_string(),
1582 });
1583 };
1584 let mut entries = type_entries(ast, target, at, refs, depth + 1)?;
1585 if !node.name.is_empty() {
1586 entries.push(Entry::named("alias", Expr::string(&scoped_name(ast, id))));
1587 }
1588 entries
1589 }
1590 TypeKind::ValueBox { boxed } => {
1594 let Some(target) = boxed.resolved() else {
1595 return Err(MaterialiseError::Unevaluated {
1596 construct: "value box target".to_string(),
1597 at: at.to_string(),
1598 });
1599 };
1600 let mut entries = type_entries(ast, target, at, refs, depth + 1)?;
1601 entries.push(Entry::named("boxed", Expr::bool(true)));
1602 entries
1603 }
1604 TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } => {
1605 let scoped = scoped_name(ast, id);
1606 note(refs, &scoped, at);
1607 vec![
1608 Entry::named("lua", Expr::string("integer")),
1609 Entry::named(
1610 "kind",
1611 Expr::string(if matches!(node.kind, TypeKind::Bitmask { .. }) {
1612 "bitmask"
1613 } else {
1614 "enum"
1615 }),
1616 ),
1617 Entry::named("bits", Expr::int(i128::from(ast.effective_bit_bound(id)))),
1618 Entry::named("ref", Expr::string(&scoped)),
1619 ]
1620 }
1621 TypeKind::Structure { .. }
1622 | TypeKind::Union { .. }
1623 | TypeKind::Bitset { .. }
1624 | TypeKind::Valuetype { .. } => {
1625 let scoped = scoped_name(ast, id);
1626 note(refs, &scoped, at);
1627 vec![
1628 Entry::named("lua", Expr::string("table")),
1629 Entry::named("kind", Expr::string(aggregate_kind(&node.kind))),
1630 Entry::named("ref", Expr::string(&scoped)),
1631 ]
1632 }
1633 TypeKind::Native => {
1634 let scoped = scoped_name(ast, id);
1635 note(refs, &scoped, at);
1636 vec![
1637 Entry::named("lua", Expr::string("any")),
1638 Entry::named("kind", Expr::string("native")),
1639 Entry::named("ref", Expr::string(&scoped)),
1640 ]
1641 }
1642 TypeKind::Any
1646 | TypeKind::ValueBase
1647 | TypeKind::ObjectRef { .. }
1648 | TypeKind::AnnotationType { .. } => {
1649 return Err(MaterialiseError::NoLanguageForm {
1650 construct: node.kind.as_str().to_string(),
1651 language: LANGUAGE,
1652 at: at.to_string(),
1653 });
1654 }
1655 })
1656}
1657
1658fn note(refs: &mut Vec<(String, String)>, name: &str, at: &str) {
1660 if refs.iter().any(|(seen, _)| seen == name) {
1661 return;
1662 }
1663 refs.push((name.to_string(), at.to_string()));
1664}
1665
1666const fn aggregate_kind(kind: &TypeKind) -> &'static str {
1668 match kind {
1669 TypeKind::Union { .. } => "union",
1670 TypeKind::Bitset { .. } => "bitset",
1671 TypeKind::Valuetype { .. } => "valuetype",
1672 _ => "struct",
1673 }
1674}
1675
1676fn primitive_entries(kind: PrimitiveKind) -> Vec<Entry> {
1689 let integer = |bits: i128, signed: bool, wraps: bool| {
1690 vec![
1691 Entry::named("lua", Expr::string("integer")),
1692 Entry::named("kind", Expr::string("int")),
1693 Entry::named("bits", Expr::int(bits)),
1694 Entry::named("signed", Expr::bool(signed)),
1695 Entry::named("wraps", Expr::bool(wraps)),
1696 ]
1697 };
1698 let character = |bits: i128| {
1699 vec![
1700 Entry::named("lua", Expr::string("integer")),
1701 Entry::named("kind", Expr::string("char")),
1702 Entry::named("bits", Expr::int(bits)),
1703 ]
1704 };
1705 match kind {
1706 PrimitiveKind::Boolean => vec![
1707 Entry::named("lua", Expr::string("boolean")),
1708 Entry::named("kind", Expr::string("boolean")),
1709 Entry::named("bits", Expr::int(8)),
1710 ],
1711 PrimitiveKind::Char => character(8),
1712 PrimitiveKind::WChar => character(16),
1713 PrimitiveKind::Octet | PrimitiveKind::UInt8 => integer(8, false, false),
1714 PrimitiveKind::Int8 => integer(8, true, false),
1715 PrimitiveKind::Int16 => integer(16, true, false),
1716 PrimitiveKind::UInt16 => integer(16, false, false),
1717 PrimitiveKind::Int32 => integer(32, true, false),
1718 PrimitiveKind::UInt32 => integer(32, false, false),
1719 PrimitiveKind::Int64 => integer(64, true, false),
1720 PrimitiveKind::UInt64 => integer(64, false, true),
1725 PrimitiveKind::Float32 => vec![
1726 Entry::named("lua", Expr::string("number")),
1727 Entry::named("kind", Expr::string("float")),
1728 Entry::named("bits", Expr::int(32)),
1729 ],
1730 PrimitiveKind::Float64 => vec![
1731 Entry::named("lua", Expr::string("number")),
1732 Entry::named("kind", Expr::string("float")),
1733 Entry::named("bits", Expr::int(64)),
1734 ],
1735 PrimitiveKind::Float128 => vec![
1738 Entry::named("lua", Expr::string("string")),
1739 Entry::named("kind", Expr::string("float")),
1740 Entry::named("bits", Expr::int(128)),
1741 ],
1742 }
1743}
1744
1745const fn char_bits(width: CharWidth) -> i128 {
1747 match width {
1748 CharWidth::Char => 8,
1749 CharWidth::WChar => 16,
1750 }
1751}
1752
1753fn bound_of(ast: &Ast, bound: Bound, at: &str) -> Result<i128, MaterialiseError> {
1761 match bound.expr() {
1762 None => Ok(0),
1763 Some(expr) => ast
1764 .eval_integral(expr)
1765 .map_err(|_| MaterialiseError::Unevaluated {
1766 construct: "bound".to_string(),
1767 at: at.to_string(),
1768 }),
1769 }
1770}
1771
1772#[allow(clippy::too_many_lines)]
1778fn default_expr(
1779 ast: &Ast,
1780 id: Id<TypeNode>,
1781 at: &str,
1782 depth: usize,
1783) -> Result<Expr, MaterialiseError> {
1784 if depth > MAX_TYPE_DEPTH {
1785 return Err(MaterialiseError::Unevaluated {
1786 construct: format!("type, whose default nests past {MAX_TYPE_DEPTH}"),
1787 at: at.to_string(),
1788 });
1789 }
1790 let node = ast.type_(id).map_err(|_| MaterialiseError::Unevaluated {
1791 construct: "type".to_string(),
1792 at: at.to_string(),
1793 })?;
1794 Ok(match &node.kind {
1795 TypeKind::Primitive(PrimitiveKind::Boolean) => Expr::bool(false),
1796 TypeKind::Primitive(PrimitiveKind::Float32 | PrimitiveKind::Float64) => {
1797 Expr::Literal(Literal::Float(lua::float_text("0")))
1798 }
1799 TypeKind::Primitive(PrimitiveKind::Float128) => Expr::string("0"),
1801 TypeKind::Primitive(_) => Expr::int(0),
1802 TypeKind::String { .. } => Expr::string(""),
1803 TypeKind::Fixed { scale, .. } => Expr::string(&zero_decimal(*scale)),
1804 TypeKind::Sequence { .. } | TypeKind::Map { .. } => Expr::Table(Table::default()),
1807 TypeKind::Array { element, dims } => {
1808 let Some(element) = element.resolved() else {
1809 return Err(MaterialiseError::Unevaluated {
1810 construct: "array element".to_string(),
1811 at: at.to_string(),
1812 });
1813 };
1814 let mut lengths = Vec::new();
1815 for dim in dims {
1816 lengths.push(ast.eval_integral(*dim).map_err(|_| {
1817 MaterialiseError::Unevaluated {
1818 construct: "array length".to_string(),
1819 at: at.to_string(),
1820 }
1821 })?);
1822 }
1823 let mut value = default_expr(ast, element, at, depth + 1)?;
1829 for length in lengths.iter().rev() {
1830 let count =
1831 usize::try_from(*length).map_err(|_| MaterialiseError::NoLanguageForm {
1832 construct: format!("an array of {length} elements"),
1833 language: LANGUAGE,
1834 at: at.to_string(),
1835 })?;
1836 value = Expr::Table(Table::of(
1837 vec![value; count]
1838 .into_iter()
1839 .map(Entry::positional)
1840 .collect(),
1841 ));
1842 }
1843 value
1844 }
1845 TypeKind::Alias { aliased } => {
1846 let Some(target) = aliased.resolved() else {
1847 return Err(MaterialiseError::Unevaluated {
1848 construct: "typedef target".to_string(),
1849 at: at.to_string(),
1850 });
1851 };
1852 default_expr(ast, target, at, depth + 1)?
1853 }
1854 TypeKind::ValueBox { boxed } => {
1855 let Some(target) = boxed.resolved() else {
1856 return Err(MaterialiseError::Unevaluated {
1857 construct: "value box target".to_string(),
1858 at: at.to_string(),
1859 });
1860 };
1861 default_expr(ast, target, at, depth + 1)?
1862 }
1863 TypeKind::Enumeration { enumerators } => {
1867 let first = enumerators
1868 .first()
1869 .and_then(|first| ast.effective_enumerator_value(*first));
1870 match first {
1871 Some(value) => integer_literal(value, at)?,
1872 None => Expr::int(0),
1873 }
1874 }
1875 TypeKind::Bitmask { .. } => Expr::int(0),
1877 TypeKind::Structure { .. }
1878 | TypeKind::Union { .. }
1879 | TypeKind::Bitset { .. }
1880 | TypeKind::Valuetype { .. } => Expr::Call {
1881 func: Box::new(constructor_place(ast, id)?),
1882 args: Vec::new(),
1883 },
1884 TypeKind::Native => Expr::bool(false),
1889 TypeKind::Any
1890 | TypeKind::ValueBase
1891 | TypeKind::ObjectRef { .. }
1892 | TypeKind::AnnotationType { .. } => {
1893 return Err(MaterialiseError::NoLanguageForm {
1894 construct: node.kind.as_str().to_string(),
1895 language: LANGUAGE,
1896 at: at.to_string(),
1897 });
1898 }
1899 })
1900}
1901
1902fn constructor_place(ast: &Ast, id: Id<TypeNode>) -> Result<Place, MaterialiseError> {
1908 Ok(declared_place(ast, id)?.field("new"))
1909}
1910
1911pub fn declared_place(ast: &Ast, id: Id<TypeNode>) -> Result<Place, MaterialiseError> {
1922 let node = ast.type_(id).map_err(|_| MaterialiseError::Unevaluated {
1923 construct: "type".to_string(),
1924 at: "a member's declared type".to_string(),
1925 })?;
1926 let mut place = Place::name(MODULE);
1927 for segment in module_chain(ast, node.module)? {
1928 place = place.field(&segment);
1929 }
1930 Ok(place.field(&node.name))
1931}
1932
1933fn is_decimal_holder(ast: &Ast, id: Id<TypeNode>) -> bool {
1940 let mut current = id;
1945 for _ in 0..MAX_TYPE_DEPTH {
1946 match ast.type_(current).map(|node| node.kind.clone()) {
1947 Ok(TypeKind::Primitive(PrimitiveKind::Float128) | TypeKind::Fixed { .. }) => {
1948 return true;
1949 }
1950 Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
1951 Some(target) => current = target,
1952 None => return false,
1953 },
1954 _ => return false,
1955 }
1956 }
1957 false
1958}
1959
1960fn constant_expr(
1966 value: &ConstValue,
1967 decimal_holder: bool,
1968 at: &str,
1969) -> Result<Expr, MaterialiseError> {
1970 let refuse = |construct: &str| MaterialiseError::NoLanguageForm {
1971 construct: construct.to_string(),
1972 language: LANGUAGE,
1973 at: at.to_string(),
1974 };
1975 Ok(match value {
1976 ConstValue::Boolean(flag) => Expr::bool(*flag),
1977 ConstValue::Integer(number) => integer_literal(*number, at)?,
1978 ConstValue::Char(character) => Expr::int(i128::from(u32::from(*character))),
1981 ConstValue::WChar(point) => Expr::int(i128::from(*point)),
1982 ConstValue::Float { mantissa, exponent } => {
1983 if decimal_holder {
1984 return Err(refuse("a `long double` constant, which has no Lua literal"));
1985 }
1986 Expr::Literal(Literal::Float(lua::float_text(&constant::float_text(
1990 mantissa,
1991 exponent.as_deref(),
1992 ))))
1993 }
1994 ConstValue::Fixed { digits, scale } => {
1995 Expr::string(&constant::fixed_decimal(digits, *scale))
1996 }
1997 ConstValue::String(text) => Expr::string(text),
1998 ConstValue::WString(points) => Expr::string(
1999 &constant::wstring_utf8(points)
2000 .ok_or_else(|| refuse("a `wstring` constant holding a lone surrogate"))?,
2001 ),
2002 _ => return Err(refuse("a constant form this binding has no literal for")),
2007 })
2008}
2009
2010fn integer_literal(value: i128, at: &str) -> Result<Expr, MaterialiseError> {
2027 if value == i128::from(i64::MIN) {
2033 return Ok(Expr::Literal(Literal::Int(
2034 "0x8000000000000000".to_string(),
2035 )));
2036 }
2037 if i64::try_from(value).is_ok() {
2038 return Ok(Expr::int(value));
2039 }
2040 if let Ok(unsigned) = u64::try_from(value) {
2041 return Ok(Expr::Literal(Literal::Int(format!("0x{unsigned:016X}"))));
2042 }
2043 Err(MaterialiseError::NoLanguageForm {
2044 construct: format!("the integer {value}, which is outside a 64-bit Lua integer"),
2045 language: LANGUAGE,
2046 at: at.to_string(),
2047 })
2048}
2049
2050#[must_use]
2057pub fn scoped_name(ast: &Ast, id: Id<TypeNode>) -> String {
2058 let Ok(any) = ast.any_of_type(id) else {
2059 return "::<unnamed>".to_string();
2060 };
2061 match ast.scoped_name_of(any) {
2062 Some(scoped) => format!("{scoped}"),
2063 None => "::<unnamed>".to_string(),
2064 }
2065}
2066
2067fn declared_scoped(chain: &[String], name: &str) -> String {
2069 let mut out = String::from("::");
2070 for segment in chain {
2071 out.push_str(segment);
2072 out.push_str("::");
2073 }
2074 out.push_str(name);
2075 out
2076}
2077
2078fn dotted(chain: &[String], name: &str) -> String {
2080 let mut segments: Vec<&str> = chain.iter().map(String::as_str).collect();
2081 segments.push(name);
2082 segments.join(".")
2083}
2084
2085fn class_name(ast: &Ast, id: Id<TypeNode>) -> String {
2087 let Ok(any) = ast.any_of_type(id) else {
2088 return "unknown".to_string();
2089 };
2090 match ast.scoped_name_of(any) {
2091 Some(scoped) => scoped.segments().join("."),
2092 None => "unknown".to_string(),
2093 }
2094}
2095
2096fn idl_spelling(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
2100 if depth > MAX_TYPE_DEPTH {
2101 return "…".to_string();
2102 }
2103 match ast
2104 .type_(id)
2105 .map(|node| (node.name.clone(), node.kind.clone()))
2106 {
2107 Ok((_, TypeKind::Primitive(kind))) => kind.as_str().to_string(),
2108 Ok((
2109 _,
2110 TypeKind::String {
2111 width: CharWidth::Char,
2112 ..
2113 },
2114 )) => "string".to_string(),
2115 Ok((
2116 _,
2117 TypeKind::String {
2118 width: CharWidth::WChar,
2119 ..
2120 },
2121 )) => "wstring".to_string(),
2122 Ok((_, TypeKind::Fixed { digits, scale })) => format!("fixed<{digits},{scale}>"),
2123 Ok((_, TypeKind::Sequence { element, .. })) => match element.resolved() {
2124 Some(element) => format!("sequence<{}>", idl_spelling(ast, element, depth + 1)),
2125 None => "sequence<?>".to_string(),
2126 },
2127 Ok((name, _)) if !name.is_empty() => scoped_name(ast, id),
2128 _ => "?".to_string(),
2129 }
2130}
2131
2132fn module_chain(ast: &Ast, module: Id<ModuleNode>) -> Result<Vec<String>, MaterialiseError> {
2141 let mut out = Vec::new();
2142 let mut current = Some(module);
2143 let mut depth = 0;
2144 while let Some(id) = current {
2145 if depth > MAX_MODULE_DEPTH {
2146 return Err(MaterialiseError::NoModule {
2147 at: format!(
2148 "#{}@{} — the parent chain runs past {MAX_MODULE_DEPTH}",
2149 module.slot(),
2150 module.generation()
2151 ),
2152 });
2153 }
2154 let node = ast.module(id).map_err(|_| MaterialiseError::NoModule {
2155 at: format!("#{}@{}", id.slot(), id.generation()),
2156 })?;
2157 if node.parent.is_none() {
2159 break;
2160 }
2161 out.push(node.name.clone());
2162 current = node.parent;
2163 depth += 1;
2164 }
2165 out.reverse();
2166 Ok(out)
2167}
2168
2169fn cats_field(name: &str, type_: &str, optional: bool) -> String {
2179 if lua::is_identifier(name) {
2180 let mark = if optional { "?" } else { "" };
2181 return format!("@field {name}{mark} {type_}");
2182 }
2183 let absence = if optional {
2184 ", absent when not set"
2185 } else {
2186 ""
2187 };
2188 format!("the member `{name}` is at [\"{name}\"] and holds {type_}{absence}")
2189}
2190
2191fn cats_class(class: &str, fields: &[String]) -> Doc {
2193 let mut doc = Doc::line(&format!("@class {class}"));
2194 for field in fields {
2195 doc.push(field);
2196 }
2197 doc
2198}
2199
2200fn optional_field(field: &str) -> String {
2202 match field.strip_prefix("@field ") {
2203 Some(rest) => match rest.split_once(' ') {
2204 Some((name, type_)) if !name.ends_with('?') => {
2205 format!("@field {name}? {type_}")
2206 }
2207 _ => field.to_string(),
2208 },
2209 None => field.to_string(),
2210 }
2211}
2212
2213fn cats_type(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
2220 if depth > MAX_TYPE_DEPTH {
2221 return "any".to_string();
2222 }
2223 let Ok(node) = ast.type_(id) else {
2224 return "any".to_string();
2225 };
2226 match &node.kind {
2227 TypeKind::Primitive(PrimitiveKind::Boolean) => "boolean".to_string(),
2228 TypeKind::Primitive(PrimitiveKind::Float32 | PrimitiveKind::Float64) => {
2229 "number".to_string()
2230 }
2231 TypeKind::Primitive(PrimitiveKind::Float128) | TypeKind::String { .. } => {
2232 "string".to_string()
2233 }
2234 TypeKind::Primitive(_) => "integer".to_string(),
2235 TypeKind::Fixed { .. } => "string".to_string(),
2236 TypeKind::Sequence { element, .. } => match element.resolved() {
2237 Some(element) => format!("{}[]", cats_type(ast, element, depth + 1)),
2238 None => "any[]".to_string(),
2239 },
2240 TypeKind::Array { element, dims } => {
2241 let mut out = match element.resolved() {
2242 Some(element) => cats_type(ast, element, depth + 1),
2243 None => "any".to_string(),
2244 };
2245 for _ in dims {
2246 out.push_str("[]");
2247 }
2248 out
2249 }
2250 TypeKind::Map { .. } => "table[]".to_string(),
2254 TypeKind::Alias { aliased } => match aliased.resolved() {
2255 Some(target) => cats_type(ast, target, depth + 1),
2256 None => "any".to_string(),
2257 },
2258 TypeKind::ValueBox { boxed } => match boxed.resolved() {
2259 Some(target) => format!("{}?", cats_type(ast, target, depth + 1)),
2260 None => "any?".to_string(),
2261 },
2262 TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } => "integer".to_string(),
2263 TypeKind::Structure { .. }
2264 | TypeKind::Union { .. }
2265 | TypeKind::Bitset { .. }
2266 | TypeKind::Valuetype { .. } => class_name(ast, id),
2267 TypeKind::Native
2268 | TypeKind::Any
2269 | TypeKind::ValueBase
2270 | TypeKind::ObjectRef { .. }
2271 | TypeKind::AnnotationType { .. } => "any".to_string(),
2272 }
2273}
2274
2275fn as_pairs(pairs: &[(String, String)]) -> Vec<(&str, &str)> {
2279 pairs
2280 .iter()
2281 .map(|(spelled, declared)| (spelled.as_str(), declared.as_str()))
2282 .collect()
2283}
2284
2285fn ghost_created_for(applied: &Applied, node: NodeId, type_name: &str) -> Option<GhostId> {
2291 let anchor = applied
2292 .origin
2293 .iter()
2294 .find(|(_, model)| **model == node)
2295 .map(|(ghost, _)| *ghost)?;
2296 let typ = applied.graph.types.lookup(type_name)?;
2297 applied
2298 .graph
2299 .nodes_of_type(typ)
2300 .map(|created| created.id)
2301 .find(|created| {
2302 applied.graph.connected(&anchor, created)
2303 || applied
2304 .graph
2305 .parts(&anchor)
2306 .any(|part| applied.graph.connected(&part.other, created))
2307 })
2308}