Skip to main content

zerodds_idl_cpp/
type_map.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Mapping IDL-Primitive → C++-Type-Strings.
4//!
5//! Folgt OMG IDL4-CPP-Mapping §7.4 Tabelle 7.5 und Tabelle 7.6 (formal/2018-07-01).
6//! Nur die Foundation-Subset (Block B): primitive Skalare, Strings, sowie die
7//! Foundation-Container [`std::vector`], [`std::array`], [`std::variant`],
8//! [`std::optional`].
9
10use zerodds_idl::ast::{FloatingType, IntegerType, PrimitiveType};
11
12use crate::error::CppGenError;
13
14/// Reservierte C++17-Schluesselwoerter, die als Identifier verboten sind.
15///
16/// Quelle: ISO/IEC 14882:2017 §5.11 (Tabelle 5). Die Liste ist intentional
17/// nicht vollstaendig — sie deckt die haeufigen Kollisionen ab, die ein
18/// IDL-Mapping treffen kann (Token-Class fuer Type-Specifier und
19/// Storage-Klassen). Erweiterbar in C5.1-b.
20pub(crate) const CPP_RESERVED: &[&str] = &[
21    "alignas",
22    "alignof",
23    "and",
24    "and_eq",
25    "asm",
26    "auto",
27    "bitand",
28    "bitor",
29    "bool",
30    "break",
31    "case",
32    "catch",
33    "char",
34    "char16_t",
35    "char32_t",
36    "class",
37    "compl",
38    "const",
39    "constexpr",
40    "const_cast",
41    "continue",
42    "decltype",
43    "default",
44    "delete",
45    "do",
46    "double",
47    "dynamic_cast",
48    "else",
49    "enum",
50    "explicit",
51    "export",
52    "extern",
53    "false",
54    "float",
55    "for",
56    "friend",
57    "goto",
58    "if",
59    "inline",
60    "int",
61    "long",
62    "mutable",
63    "namespace",
64    "new",
65    "noexcept",
66    "not",
67    "not_eq",
68    "nullptr",
69    "operator",
70    "or",
71    "or_eq",
72    "private",
73    "protected",
74    "public",
75    "register",
76    "reinterpret_cast",
77    "return",
78    "short",
79    "signed",
80    "sizeof",
81    "static",
82    "static_assert",
83    "static_cast",
84    "struct",
85    "switch",
86    "template",
87    "this",
88    "thread_local",
89    "throw",
90    "true",
91    "try",
92    "typedef",
93    "typeid",
94    "typename",
95    "union",
96    "unsigned",
97    "using",
98    "virtual",
99    "void",
100    "volatile",
101    "wchar_t",
102    "while",
103    "xor",
104    "xor_eq",
105];
106
107/// Prueft, ob ein Identifier ein C++-Keyword ist.
108#[must_use]
109pub fn is_reserved(name: &str) -> bool {
110    CPP_RESERVED.contains(&name)
111}
112
113/// Pruefen + Fehler-Konversion: liefert Err, wenn `name` reserviert ist.
114///
115/// # Errors
116/// Gibt [`CppGenError::InvalidName`] zurueck, wenn `name` ein
117/// reserviertes C++-Keyword ist.
118pub fn check_identifier(name: &str) -> Result<(), CppGenError> {
119    if is_reserved(name) {
120        return Err(CppGenError::InvalidName {
121            name: name.to_string(),
122            reason: "reserved C++ keyword".to_string(),
123        });
124    }
125    Ok(())
126}
127
128/// Mappt eine [`PrimitiveType`] auf den C++-Typ-Ausdruck (als `&'static str`).
129///
130/// Spec-Referenz: §7.4 Tabelle 7.5.
131#[must_use]
132pub fn primitive_to_cpp(p: PrimitiveType) -> &'static str {
133    match p {
134        PrimitiveType::Boolean => "bool",
135        PrimitiveType::Octet => "uint8_t",
136        PrimitiveType::Char => "char",
137        PrimitiveType::WideChar => "wchar_t",
138        PrimitiveType::Integer(i) => integer_to_cpp(i),
139        PrimitiveType::Floating(f) => floating_to_cpp(f),
140    }
141}
142
143/// Mapping fuer Integer-Subtypen.
144#[must_use]
145pub fn integer_to_cpp(i: IntegerType) -> &'static str {
146    match i {
147        IntegerType::Short | IntegerType::Int16 => "int16_t",
148        IntegerType::Long | IntegerType::Int32 => "int32_t",
149        IntegerType::LongLong | IntegerType::Int64 => "int64_t",
150        IntegerType::UShort | IntegerType::UInt16 => "uint16_t",
151        IntegerType::ULong | IntegerType::UInt32 => "uint32_t",
152        IntegerType::ULongLong | IntegerType::UInt64 => "uint64_t",
153        IntegerType::Int8 => "int8_t",
154        IntegerType::UInt8 => "uint8_t",
155    }
156}
157
158/// Mapping fuer Floating-Subtypen. `long double` wird als
159/// [`CppGenError::UnsupportedConstruct`] gemeldet (Block-E-außerhalb des aktuellen Scopes).
160#[must_use]
161pub fn floating_to_cpp(f: FloatingType) -> &'static str {
162    match f {
163        FloatingType::Float => "float",
164        FloatingType::Double => "double",
165        FloatingType::LongDouble => "long double",
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    #![allow(clippy::expect_used, clippy::panic)]
172    use super::*;
173
174    #[test]
175    fn primitive_boolean() {
176        assert_eq!(primitive_to_cpp(PrimitiveType::Boolean), "bool");
177    }
178
179    #[test]
180    fn primitive_octet() {
181        assert_eq!(primitive_to_cpp(PrimitiveType::Octet), "uint8_t");
182    }
183
184    #[test]
185    fn primitive_char() {
186        assert_eq!(primitive_to_cpp(PrimitiveType::Char), "char");
187    }
188
189    #[test]
190    fn primitive_wchar() {
191        assert_eq!(primitive_to_cpp(PrimitiveType::WideChar), "wchar_t");
192    }
193
194    #[test]
195    fn integer_short_signed_unsigned() {
196        assert_eq!(integer_to_cpp(IntegerType::Short), "int16_t");
197        assert_eq!(integer_to_cpp(IntegerType::UShort), "uint16_t");
198    }
199
200    #[test]
201    fn integer_long_signed_unsigned() {
202        assert_eq!(integer_to_cpp(IntegerType::Long), "int32_t");
203        assert_eq!(integer_to_cpp(IntegerType::ULong), "uint32_t");
204    }
205
206    #[test]
207    fn integer_long_long_signed_unsigned() {
208        assert_eq!(integer_to_cpp(IntegerType::LongLong), "int64_t");
209        assert_eq!(integer_to_cpp(IntegerType::ULongLong), "uint64_t");
210    }
211
212    #[test]
213    fn integer_explicit_widths() {
214        assert_eq!(integer_to_cpp(IntegerType::Int8), "int8_t");
215        assert_eq!(integer_to_cpp(IntegerType::UInt8), "uint8_t");
216        assert_eq!(integer_to_cpp(IntegerType::Int16), "int16_t");
217        assert_eq!(integer_to_cpp(IntegerType::UInt16), "uint16_t");
218        assert_eq!(integer_to_cpp(IntegerType::Int32), "int32_t");
219        assert_eq!(integer_to_cpp(IntegerType::UInt32), "uint32_t");
220        assert_eq!(integer_to_cpp(IntegerType::Int64), "int64_t");
221        assert_eq!(integer_to_cpp(IntegerType::UInt64), "uint64_t");
222    }
223
224    #[test]
225    fn floating_float_double() {
226        assert_eq!(floating_to_cpp(FloatingType::Float), "float");
227        assert_eq!(floating_to_cpp(FloatingType::Double), "double");
228    }
229
230    #[test]
231    fn reserved_class_is_rejected() {
232        assert!(is_reserved("class"));
233        assert!(check_identifier("class").is_err());
234    }
235
236    #[test]
237    fn reserved_int_is_rejected() {
238        assert!(is_reserved("int"));
239    }
240
241    #[test]
242    fn non_reserved_identifier_passes() {
243        assert!(!is_reserved("Foo"));
244        assert!(check_identifier("Foo").is_ok());
245    }
246
247    #[test]
248    fn check_returns_invalidname_with_reason() {
249        let err = check_identifier("template").expect_err("must reject 'template'");
250        match err {
251            CppGenError::InvalidName { reason, name } => {
252                assert_eq!(name, "template");
253                assert!(reason.to_lowercase().contains("reserved"));
254            }
255            other => panic!("unexpected err variant: {other:?}"),
256        }
257    }
258}