1use crate::codegen::type_hash::compute_type_id;
5
6#[derive(Debug, Clone)]
7pub struct StructSpec {
8 pub namespace: Vec<String>,
9 pub name: String,
10 pub size: u32,
11 pub alignment: u8,
12 pub fields: Vec<FieldSpec>,
13}
14
15impl StructSpec {
16 pub fn new(namespace: Vec<String>, name: impl Into<String>) -> Self {
17 Self {
18 namespace,
19 name: name.into(),
20 size: 0,
21 alignment: 1,
22 fields: Vec::new(),
23 }
24 }
25
26 #[must_use]
27 pub fn with_layout(mut self, size: u32, alignment: u8) -> Self {
28 self.size = size;
29 self.alignment = alignment;
30 self
31 }
32
33 #[must_use]
34 pub fn with_fields(mut self, fields: Vec<FieldSpec>) -> Self {
35 self.fields = fields;
36 self
37 }
38
39 fn fully_qualified_name(&self) -> String {
40 if self.namespace.is_empty() {
41 self.name.clone()
42 } else {
43 format!("{}::{}", self.namespace.join("::"), self.name)
44 }
45 }
46
47 fn type_path(&self) -> String {
48 self.fully_qualified_name()
49 }
50
51 fn is_variable(&self) -> bool {
52 self.fields.iter().any(|f| f.kind.is_dynamic())
53 }
54}
55
56#[derive(Debug, Clone)]
57pub struct FieldSpec {
58 pub name: String,
59 pub offset: u32,
60 pub size: u32,
61 pub alignment: u8,
62 pub kind: FieldKind,
63 pub element_type: Option<String>,
64}
65
66impl FieldSpec {
67 pub fn new(
68 name: impl Into<String>,
69 offset: u32,
70 size: u32,
71 alignment: u8,
72 kind: FieldKind,
73 ) -> Self {
74 Self {
75 name: name.into(),
76 offset,
77 size,
78 alignment,
79 kind,
80 element_type: None,
81 }
82 }
83
84 #[must_use]
85 pub fn with_element_type(mut self, path: impl Into<String>) -> Self {
86 self.element_type = Some(path.into());
87 self
88 }
89
90 fn size_literal(&self) -> String {
91 if self.kind.is_dynamic() {
92 "0xFFFF_FFFF".to_string()
93 } else {
94 self.size.to_string()
95 }
96 }
97
98 fn element_expr(&self) -> String {
99 match (&self.element_type, &self.kind) {
100 (Some(path), _) => format!("Some({path}::type_descriptor())"),
101 (None, FieldKind::Struct { type_path }) => {
102 format!("Some({type_path}::type_descriptor())")
103 }
104 _ => "None".to_string(),
105 }
106 }
107
108 fn render(&self) -> String {
109 format!(
110 "::hdds::core::types::FieldLayout {{\n name: \"{}\",\n offset_bytes: {},\n field_type: {},\n alignment: {},\n size_bytes: {},\n element_type: {},\n }}",
111 self.name,
112 self.offset,
113 self.kind.field_type_expr(),
114 self.alignment,
115 self.size_literal(),
116 self.element_expr()
117 )
118 }
119}
120
121#[derive(Debug, Clone)]
122pub enum FieldKind {
123 Primitive(PrimitiveType),
124 Sequence,
125 Array,
126 Struct { type_path: String },
127 String,
128}
129
130impl FieldKind {
131 fn field_type_expr(&self) -> String {
132 match self {
133 FieldKind::Primitive(p) => format!(
134 "::hdds::core::types::FieldType::Primitive({})",
135 p.primitive_expr()
136 ),
137 FieldKind::Sequence => "::hdds::core::types::FieldType::Sequence".to_string(),
138 FieldKind::Array => "::hdds::core::types::FieldType::Array".to_string(),
139 FieldKind::Struct { .. } => "::hdds::core::types::FieldType::Struct".to_string(),
140 FieldKind::String => "::hdds::core::types::FieldType::String".to_string(),
141 }
142 }
143
144 fn is_dynamic(&self) -> bool {
145 matches!(self, FieldKind::Sequence | FieldKind::String)
146 }
147}
148
149#[derive(Debug, Clone)]
150pub enum PrimitiveType {
151 U8,
152 U16,
153 U32,
154 U64,
155 I8,
156 I16,
157 I32,
158 I64,
159 F32,
160 F64,
161 Bool,
162}
163
164impl PrimitiveType {
165 fn primitive_expr(&self) -> &'static str {
167 match self {
168 PrimitiveType::U8 => "::hdds::core::types::PrimitiveKind::U8",
169 PrimitiveType::U16 => "::hdds::core::types::PrimitiveKind::U16",
170 PrimitiveType::U32 => "::hdds::core::types::PrimitiveKind::U32",
171 PrimitiveType::U64 => "::hdds::core::types::PrimitiveKind::U64",
172 PrimitiveType::I8 => "::hdds::core::types::PrimitiveKind::I8",
173 PrimitiveType::I16 => "::hdds::core::types::PrimitiveKind::I16",
174 PrimitiveType::I32 => "::hdds::core::types::PrimitiveKind::I32",
175 PrimitiveType::I64 => "::hdds::core::types::PrimitiveKind::I64",
176 PrimitiveType::F32 => "::hdds::core::types::PrimitiveKind::F32",
177 PrimitiveType::F64 => "::hdds::core::types::PrimitiveKind::F64",
178 PrimitiveType::Bool => "::hdds::core::types::PrimitiveKind::Bool",
179 }
180 }
181}
182
183pub fn emit_type_descriptor(spec: &StructSpec) -> String {
185 let fq_name = spec.fully_qualified_name();
186 let type_path = spec.type_path();
187 let type_id = compute_type_id(&fq_name);
188 let type_id_literal = format!("0x{type_id:08X}");
189 let is_variable = spec.is_variable();
190 let size_literal = if is_variable {
191 "0xFFFF_FFFF".to_string()
192 } else {
193 spec.size.to_string()
194 };
195
196 let fields: Vec<String> = spec.fields.iter().map(FieldSpec::render).collect();
197 let fields_block = if fields.is_empty() {
198 " ".to_string()
199 } else {
200 fields
201 .into_iter()
202 .map(|f| format!(" {f},"))
203 .collect::<Vec<_>>()
204 .join("\n")
205 };
206
207 format!(
208 "impl ::hdds::api::DDS for {type_path} {{\n fn type_descriptor() -> &'static ::hdds::core::types::TypeDescriptor {{\n static DESC: ::hdds::core::types::TypeDescriptor = ::hdds::core::types::TypeDescriptor {{\n type_id: {type_id_literal},\n type_name: \"{fq_name}\",\n size_bytes: {size_literal},\n alignment: {alignment},\n is_variable_size: {is_variable},\n fields: &[\n{fields_block}\n ],\n }};\n &DESC\n }}\n}}",
209 type_path = type_path,
210 type_id_literal = type_id_literal,
211 fq_name = fq_name,
212 size_literal = size_literal,
213 alignment = spec.alignment,
214 is_variable = if is_variable { "true" } else { "false" },
215 fields_block = fields_block,
216 )
217}