1#![forbid(unsafe_code)]
50#![warn(missing_docs)]
51#![allow(
52 clippy::manual_pattern_char_comparison,
53 clippy::if_same_then_else,
54 clippy::collapsible_if,
55 clippy::useless_conversion,
56 clippy::approx_constant
57)]
58
59pub(crate) mod amqp;
60pub(crate) mod bitset;
61pub mod c_mode;
62pub(crate) mod corba_traits;
63pub mod dcps;
64pub mod emitter;
65pub mod error;
66pub mod psm_cxx;
67pub mod qos;
68pub mod rpc;
69pub mod status;
70pub mod type_map;
71pub(crate) mod verbatim;
72
73pub use c_mode::{CGenOptions, generate_c_header};
74pub use error::CppGenError;
75pub use psm_cxx::{
76 emit_condition_skeleton, emit_core_basics, emit_exception_hierarchy,
77 emit_full_psm_cxx_skeleton, emit_listener_skeleton, emit_psm_cxx_includes,
78 emit_reference_value_pattern,
79};
80
81use zerodds_idl::ast::Specification;
82
83#[derive(Debug, Clone)]
85pub struct CppGenOptions {
86 pub namespace_prefix: Option<String>,
89 pub include_guard_prefix: Option<String>,
93 pub indent_width: usize,
95 pub emit_amqp_helpers: bool,
102 pub emit_corba_traits: bool,
107}
108
109impl Default for CppGenOptions {
110 fn default() -> Self {
111 Self {
112 namespace_prefix: None,
113 include_guard_prefix: None,
114 indent_width: 4,
115 emit_amqp_helpers: false,
116 emit_corba_traits: false,
117 }
118 }
119}
120
121pub(crate) const TIME_DURATION_TYPES: &[(&str, &str)] = &[
126 ("Time_t", "DDS::Time_t"),
127 ("Duration_t", "DDS::Duration_t"),
128 ("Time", "DDS::Time_t"),
129 ("Duration", "DDS::Duration_t"),
130];
131
132pub fn generate_cpp_header(
143 ast: &Specification,
144 opts: &CppGenOptions,
145) -> Result<String, CppGenError> {
146 let mut out = emitter::emit_header(ast, opts)?;
147 if opts.emit_amqp_helpers {
148 amqp::emit_amqp_helpers(&mut out, ast)?;
149 }
150 if opts.emit_corba_traits {
151 corba_traits::emit_corba_traits(&mut out, ast)?;
152 }
153 Ok(out)
154}
155
156pub fn generate_cpp_header_with_corba_traits(
164 ast: &Specification,
165 opts: &CppGenOptions,
166) -> Result<String, CppGenError> {
167 let opts = CppGenOptions {
168 emit_corba_traits: true,
169 ..opts.clone()
170 };
171 generate_cpp_header(ast, &opts)
172}
173
174pub fn generate_cpp_header_with_amqp(
183 ast: &Specification,
184 opts: &CppGenOptions,
185) -> Result<String, CppGenError> {
186 let opts = CppGenOptions {
187 emit_amqp_helpers: true,
188 ..opts.clone()
189 };
190 generate_cpp_header(ast, &opts)
191}
192
193#[cfg(test)]
194mod tests {
195 #![allow(clippy::expect_used, clippy::panic)]
196 use super::*;
197 use zerodds_idl::config::ParserConfig;
198
199 fn gen_cpp(src: &str) -> String {
200 let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse must succeed");
201 generate_cpp_header(&ast, &CppGenOptions::default()).expect("gen must succeed")
202 }
203
204 #[test]
205 fn empty_source_emits_only_preamble() {
206 let cpp = gen_cpp("");
207 assert!(cpp.contains("#pragma once"));
208 assert!(cpp.contains("Generated by zerodds idl-cpp"));
209 assert!(!cpp.contains("namespace M {"));
211 }
212
213 #[test]
214 fn empty_module_emits_namespace() {
215 let cpp = gen_cpp("module M {};");
216 assert!(cpp.contains("namespace M {"));
217 assert!(cpp.contains("} // namespace M"));
218 }
219
220 #[test]
221 fn three_level_modules_nest() {
222 let cpp = gen_cpp("module A { module B { module C {}; }; };");
223 assert!(cpp.contains("namespace A {"));
224 assert!(cpp.contains("namespace B {"));
225 assert!(cpp.contains("namespace C {"));
226 assert!(cpp.contains("} // namespace C"));
227 assert!(cpp.contains("} // namespace B"));
228 assert!(cpp.contains("} // namespace A"));
229 }
230
231 #[test]
232 fn primitive_struct_member_uses_correct_cpp_types() {
233 let cpp = gen_cpp(
234 "struct S { boolean b; octet o; short s; long l; long long ll; \
235 unsigned short us; unsigned long ul; unsigned long long ull; \
236 float f; double d; };",
237 );
238 assert!(cpp.contains("bool b_;"));
239 assert!(cpp.contains("uint8_t o_;"));
240 assert!(cpp.contains("int16_t s_;"));
241 assert!(cpp.contains("int32_t l_;"));
242 assert!(cpp.contains("int64_t ll_;"));
243 assert!(cpp.contains("uint16_t us_;"));
244 assert!(cpp.contains("uint32_t ul_;"));
245 assert!(cpp.contains("uint64_t ull_;"));
246 assert!(cpp.contains("float f_;"));
247 assert!(cpp.contains("double d_;"));
248 }
249
250 #[test]
251 fn string_member_requires_string_include() {
252 let cpp = gen_cpp("struct S { string name; };");
253 assert!(cpp.contains("#include <string>"));
254 assert!(cpp.contains("std::string name_;"));
255 }
256
257 #[test]
258 fn sequence_member_uses_vector() {
259 let cpp = gen_cpp("struct S { sequence<long> data; };");
260 assert!(cpp.contains("#include <vector>"));
261 assert!(cpp.contains("std::vector<int32_t> data_;"));
262 }
263
264 #[test]
265 fn array_member_uses_std_array() {
266 let cpp = gen_cpp("struct S { long matrix[3][4]; };");
267 assert!(cpp.contains("#include <array>"));
268 assert!(cpp.contains("std::array<std::array<int32_t, 4>, 3>"));
269 }
270
271 #[test]
272 fn enum_emits_enum_class_int32_t() {
273 let cpp = gen_cpp("enum Color { RED, GREEN, BLUE };");
274 assert!(cpp.contains("enum class Color : int32_t"));
275 assert!(cpp.contains("RED,"));
276 assert!(cpp.contains("BLUE,"));
277 }
278
279 #[test]
280 fn typedef_emits_using_alias() {
281 let cpp = gen_cpp("typedef long MyInt;");
282 assert!(cpp.contains("using MyInt = int32_t;"));
283 }
284
285 #[test]
286 fn inheritance_emits_public_base() {
287 let cpp = gen_cpp("struct Parent { long x; }; struct Child : Parent { long y; };");
288 assert!(cpp.contains("class Child : public Parent"));
289 }
290
291 #[test]
292 fn keyed_struct_marker_appears() {
293 let cpp = gen_cpp("struct S { @key long id; long val; };");
294 assert!(cpp.contains("// @key"));
295 }
296
297 #[test]
298 fn optional_member_uses_std_optional() {
299 let cpp = gen_cpp("struct S { @optional long maybe; };");
300 assert!(cpp.contains("#include <optional>"));
301 assert!(cpp.contains("std::optional<int32_t>"));
302 }
303
304 #[test]
305 fn exception_inherits_std_exception() {
306 let cpp = gen_cpp("exception NotFound { string what_; };");
307 assert!(cpp.contains("#include <exception>"));
308 assert!(cpp.contains("class NotFound : public std::exception"));
309 }
310
311 #[test]
312 fn union_uses_std_variant() {
313 let cpp = gen_cpp(
314 "union U switch (long) { case 1: long a; case 2: double b; default: octet c; };",
315 );
316 assert!(cpp.contains("#include <variant>"));
317 assert!(cpp.contains("std::variant<"));
318 assert!(cpp.contains("// case default"));
319 }
320
321 #[test]
322 fn time_t_member_maps_to_dds_time_t() {
323 let cpp = gen_cpp("struct S { Time_t t; };");
324 assert!(cpp.contains("DDS::Time_t"));
325 }
326
327 #[test]
328 fn duration_t_member_maps_to_dds_duration_t() {
329 let cpp = gen_cpp("struct S { Duration_t d; };");
330 assert!(cpp.contains("DDS::Duration_t"));
331 }
332
333 #[test]
334 fn reserved_field_name_is_rejected() {
335 let ast = zerodds_idl::parse("struct S { long class_field; };", &ParserConfig::default())
336 .expect("parse");
337 let res = type_map::check_identifier("class");
341 assert!(matches!(res, Err(CppGenError::InvalidName { .. })));
342 let _ = ast; }
344
345 #[test]
346 fn empty_source_includes_cstdint() {
347 let cpp = gen_cpp("");
348 assert!(cpp.contains("#include <cstdint>"));
349 }
350
351 #[test]
352 fn header_starts_with_generated_marker() {
353 let cpp = gen_cpp("");
354 assert!(cpp.starts_with("// Generated by zerodds idl-cpp."));
355 }
356
357 #[test]
358 fn pragma_once_appears_exactly_once() {
359 let cpp = gen_cpp("module M { struct S { long x; }; };");
360 let count = cpp.matches("#pragma once").count();
361 assert_eq!(count, 1);
362 }
363
364 #[test]
365 fn struct_has_default_constructor() {
366 let cpp = gen_cpp("struct S { long x; };");
367 assert!(cpp.contains("S() = default;"));
368 assert!(cpp.contains("~S() = default;"));
369 }
370
371 #[test]
372 fn struct_has_mutable_and_const_getter() {
373 let cpp = gen_cpp("struct S { long x; };");
374 assert!(cpp.contains("int32_t& x()"));
376 assert!(cpp.contains("const int32_t& x() const"));
377 }
378
379 #[test]
380 fn struct_has_setter() {
381 let cpp = gen_cpp("struct S { long x; };");
382 assert!(cpp.contains("void x(const int32_t& value)"));
383 }
384
385 #[test]
386 fn namespace_prefix_option_wraps_output() {
387 let ast =
388 zerodds_idl::parse("struct S { long x; };", &ParserConfig::default()).expect("parse");
389 let opts = CppGenOptions {
390 namespace_prefix: Some("zerodds".into()),
391 ..Default::default()
392 };
393 let cpp = generate_cpp_header(&ast, &opts).expect("gen");
394 assert!(cpp.contains("namespace zerodds {"));
395 assert!(cpp.contains("} // namespace zerodds"));
396 }
397
398 #[test]
399 fn non_service_interface_emits_pure_virtual_class() {
400 let ast = zerodds_idl::parse("interface I { void op(); };", &ParserConfig::default())
401 .expect("parse");
402 let cpp = generate_cpp_header(&ast, &CppGenOptions::default()).expect("ok");
403 assert!(cpp.contains("class I"));
404 assert!(cpp.contains("virtual ~I()"));
405 assert!(cpp.contains("= 0;"));
406 }
407
408 #[test]
409 fn const_decl_emits_constexpr() {
410 let cpp = gen_cpp("const long MAX = 100;");
411 assert!(cpp.contains("constexpr int32_t MAX = 100;"));
412 }
413
414 #[test]
415 fn options_have_sensible_defaults() {
416 let o = CppGenOptions::default();
417 assert_eq!(o.indent_width, 4);
418 assert!(o.namespace_prefix.is_none());
419 assert!(o.include_guard_prefix.is_none());
420 }
421
422 #[test]
423 fn options_clone_works() {
424 let o = CppGenOptions {
425 namespace_prefix: Some("foo".into()),
426 include_guard_prefix: Some("FOO_".into()),
427 indent_width: 2,
428 emit_amqp_helpers: false,
429 emit_corba_traits: false,
430 };
431 let cloned = o.clone();
432 assert_eq!(cloned.indent_width, 2);
433 assert_eq!(cloned.namespace_prefix.as_deref(), Some("foo"));
434 }
435}