Skip to main content

hdds_gen/codegen/
rust_backend.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025-2026 naskel.com
3
4use 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    // @audit-ok: Simple pattern matching (cyclo 12, cogni 1) - type to string literal mapping
166    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
183/// Emit Rust code that registers a `TypeDescriptor` for the provided structure.
184pub 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}