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,
101 pub emit_corba_traits: bool,
106}
107
108impl Default for CppGenOptions {
109 fn default() -> Self {
110 Self {
111 namespace_prefix: None,
112 include_guard_prefix: None,
113 indent_width: 4,
114 emit_amqp_helpers: false,
115 emit_corba_traits: false,
116 }
117 }
118}
119
120pub(crate) const TIME_DURATION_TYPES: &[(&str, &str)] = &[
125 ("Time_t", "DDS::Time_t"),
126 ("Duration_t", "DDS::Duration_t"),
127 ("Time", "DDS::Time_t"),
128 ("Duration", "DDS::Duration_t"),
129];
130
131pub fn generate_cpp_header(
142 ast: &Specification,
143 opts: &CppGenOptions,
144) -> Result<String, CppGenError> {
145 let mut out = emitter::emit_header(ast, opts)?;
146 if opts.emit_amqp_helpers {
147 amqp::emit_amqp_helpers(&mut out, ast)?;
148 }
149 if opts.emit_corba_traits {
150 corba_traits::emit_corba_traits(&mut out, ast)?;
151 }
152 Ok(out)
153}
154
155pub fn generate_cpp_header_with_corba_traits(
163 ast: &Specification,
164 opts: &CppGenOptions,
165) -> Result<String, CppGenError> {
166 let opts = CppGenOptions {
167 emit_corba_traits: true,
168 ..opts.clone()
169 };
170 generate_cpp_header(ast, &opts)
171}
172
173pub fn generate_cpp_header_with_amqp(
182 ast: &Specification,
183 opts: &CppGenOptions,
184) -> Result<String, CppGenError> {
185 let opts = CppGenOptions {
186 emit_amqp_helpers: true,
187 ..opts.clone()
188 };
189 generate_cpp_header(ast, &opts)
190}
191
192#[cfg(test)]
193mod tests {
194 #![allow(clippy::expect_used, clippy::panic)]
195 use super::*;
196 use zerodds_idl::config::ParserConfig;
197
198 fn gen_cpp(src: &str) -> String {
199 let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse must succeed");
200 generate_cpp_header(&ast, &CppGenOptions::default()).expect("gen must succeed")
201 }
202
203 #[test]
204 fn empty_source_emits_only_preamble() {
205 let cpp = gen_cpp("");
206 assert!(cpp.contains("#pragma once"));
207 assert!(cpp.contains("Generated by zerodds idl-cpp"));
208 assert!(!cpp.contains("namespace M {"));
210 }
211
212 #[test]
213 fn empty_module_emits_namespace() {
214 let cpp = gen_cpp("module M {};");
215 assert!(cpp.contains("namespace M {"));
216 assert!(cpp.contains("} // namespace M"));
217 }
218
219 #[test]
220 fn three_level_modules_nest() {
221 let cpp = gen_cpp("module A { module B { module C {}; }; };");
222 assert!(cpp.contains("namespace A {"));
223 assert!(cpp.contains("namespace B {"));
224 assert!(cpp.contains("namespace C {"));
225 assert!(cpp.contains("} // namespace C"));
226 assert!(cpp.contains("} // namespace B"));
227 assert!(cpp.contains("} // namespace A"));
228 }
229
230 #[test]
231 fn primitive_struct_member_uses_correct_cpp_types() {
232 let cpp = gen_cpp(
233 "struct S { boolean b; octet o; short s; long l; long long ll; \
234 unsigned short us; unsigned long ul; unsigned long long ull; \
235 float f; double d; };",
236 );
237 assert!(cpp.contains("bool b_;"));
238 assert!(cpp.contains("uint8_t o_;"));
239 assert!(cpp.contains("int16_t s_;"));
240 assert!(cpp.contains("int32_t l_;"));
241 assert!(cpp.contains("int64_t ll_;"));
242 assert!(cpp.contains("uint16_t us_;"));
243 assert!(cpp.contains("uint32_t ul_;"));
244 assert!(cpp.contains("uint64_t ull_;"));
245 assert!(cpp.contains("float f_;"));
246 assert!(cpp.contains("double d_;"));
247 }
248
249 #[test]
250 fn string_member_requires_string_include() {
251 let cpp = gen_cpp("struct S { string name; };");
252 assert!(cpp.contains("#include <string>"));
253 assert!(cpp.contains("std::string name_;"));
254 }
255
256 #[test]
257 fn sequence_member_uses_vector() {
258 let cpp = gen_cpp("struct S { sequence<long> data; };");
259 assert!(cpp.contains("#include <vector>"));
260 assert!(cpp.contains("std::vector<int32_t> data_;"));
261 }
262
263 #[test]
264 fn array_member_uses_std_array() {
265 let cpp = gen_cpp("struct S { long matrix[3][4]; };");
266 assert!(cpp.contains("#include <array>"));
267 assert!(cpp.contains("std::array<std::array<int32_t, 4>, 3>"));
268 }
269
270 #[test]
271 fn enum_emits_enum_class_int32_t() {
272 let cpp = gen_cpp("enum Color { RED, GREEN, BLUE };");
273 assert!(cpp.contains("enum class Color : int32_t"));
274 assert!(cpp.contains("RED,"));
275 assert!(cpp.contains("BLUE,"));
276 }
277
278 #[test]
279 fn typedef_emits_using_alias() {
280 let cpp = gen_cpp("typedef long MyInt;");
281 assert!(cpp.contains("using MyInt = int32_t;"));
282 }
283
284 #[test]
285 fn inheritance_emits_public_base() {
286 let cpp = gen_cpp("struct Parent { long x; }; struct Child : Parent { long y; };");
287 assert!(cpp.contains("class Child : public Parent"));
288 }
289
290 #[test]
291 fn keyed_struct_marker_appears() {
292 let cpp = gen_cpp("struct S { @key long id; long val; };");
293 assert!(cpp.contains("// @key"));
294 }
295
296 #[test]
297 fn optional_member_uses_std_optional() {
298 let cpp = gen_cpp("struct S { @optional long maybe; };");
299 assert!(cpp.contains("#include <optional>"));
300 assert!(cpp.contains("std::optional<int32_t>"));
301 }
302
303 #[test]
304 fn exception_inherits_std_exception() {
305 let cpp = gen_cpp("exception NotFound { string what_; };");
306 assert!(cpp.contains("#include <exception>"));
307 assert!(cpp.contains("class NotFound : public std::exception"));
308 }
309
310 #[test]
311 fn union_uses_std_variant() {
312 let cpp = gen_cpp(
313 "union U switch (long) { case 1: long a; case 2: double b; default: octet c; };",
314 );
315 assert!(cpp.contains("#include <variant>"));
316 assert!(cpp.contains("std::variant<"));
317 assert!(cpp.contains("// case default"));
318 }
319
320 #[test]
321 fn time_t_member_maps_to_dds_time_t() {
322 let cpp = gen_cpp("struct S { Time_t t; };");
323 assert!(cpp.contains("DDS::Time_t"));
324 }
325
326 #[test]
327 fn duration_t_member_maps_to_dds_duration_t() {
328 let cpp = gen_cpp("struct S { Duration_t d; };");
329 assert!(cpp.contains("DDS::Duration_t"));
330 }
331
332 #[test]
333 fn reserved_field_name_is_rejected() {
334 let ast = zerodds_idl::parse("struct S { long class_field; };", &ParserConfig::default())
335 .expect("parse");
336 let res = type_map::check_identifier("class");
340 assert!(matches!(res, Err(CppGenError::InvalidName { .. })));
341 let _ = ast; }
343
344 #[test]
345 fn empty_source_includes_cstdint() {
346 let cpp = gen_cpp("");
347 assert!(cpp.contains("#include <cstdint>"));
348 }
349
350 #[test]
351 fn header_starts_with_generated_marker() {
352 let cpp = gen_cpp("");
353 assert!(cpp.starts_with("// Generated by zerodds idl-cpp."));
354 }
355
356 #[test]
357 fn pragma_once_appears_exactly_once() {
358 let cpp = gen_cpp("module M { struct S { long x; }; };");
359 let count = cpp.matches("#pragma once").count();
360 assert_eq!(count, 1);
361 }
362
363 #[test]
364 fn struct_has_default_constructor() {
365 let cpp = gen_cpp("struct S { long x; };");
366 assert!(cpp.contains("S() = default;"));
367 assert!(cpp.contains("~S() = default;"));
368 }
369
370 #[test]
371 fn struct_has_mutable_and_const_getter() {
372 let cpp = gen_cpp("struct S { long x; };");
373 assert!(cpp.contains("int32_t& x()"));
375 assert!(cpp.contains("const int32_t& x() const"));
376 }
377
378 #[test]
379 fn struct_has_setter() {
380 let cpp = gen_cpp("struct S { long x; };");
381 assert!(cpp.contains("void x(const int32_t& value)"));
382 }
383
384 #[test]
385 fn struct_has_field_order_constructor() {
386 let cpp = gen_cpp("struct S { long celsius; string sensor_id; };");
389 assert!(
390 cpp.contains("S(int32_t celsius, std::string sensor_id)"),
391 "field-order ctor signature missing:\n{cpp}"
392 );
393 assert!(
394 cpp.contains(": celsius_(std::move(celsius)), sensor_id_(std::move(sensor_id)) {}"),
395 "member-init list missing:\n{cpp}"
396 );
397 assert!(cpp.contains("S() = default;"));
399 }
400
401 #[test]
402 fn zero_field_struct_has_no_field_order_constructor() {
403 let cpp = gen_cpp("struct Empty { };");
406 assert!(cpp.contains("Empty() = default;"));
407 assert_eq!(
410 cpp.matches("Empty(").count(),
411 2, "unexpected extra constructor for zero-field struct:\n{cpp}"
413 );
414 assert!(!cpp.contains("std::move"));
415 }
416
417 #[test]
418 fn namespace_prefix_option_wraps_output() {
419 let ast =
420 zerodds_idl::parse("struct S { long x; };", &ParserConfig::default()).expect("parse");
421 let opts = CppGenOptions {
422 namespace_prefix: Some("zerodds".into()),
423 ..Default::default()
424 };
425 let cpp = generate_cpp_header(&ast, &opts).expect("gen");
426 assert!(cpp.contains("namespace zerodds {"));
427 assert!(cpp.contains("} // namespace zerodds"));
428 }
429
430 #[test]
431 fn non_service_interface_emits_pure_virtual_class() {
432 let ast = zerodds_idl::parse("interface I { void op(); };", &ParserConfig::default())
433 .expect("parse");
434 let cpp = generate_cpp_header(&ast, &CppGenOptions::default()).expect("ok");
435 assert!(cpp.contains("class I"));
436 assert!(cpp.contains("virtual ~I()"));
437 assert!(cpp.contains("= 0;"));
438 }
439
440 #[test]
441 fn const_decl_emits_constexpr() {
442 let cpp = gen_cpp("const long MAX = 100;");
443 assert!(cpp.contains("constexpr int32_t MAX = 100;"));
444 }
445
446 #[test]
447 fn options_have_sensible_defaults() {
448 let o = CppGenOptions::default();
449 assert_eq!(o.indent_width, 4);
450 assert!(o.namespace_prefix.is_none());
451 assert!(o.include_guard_prefix.is_none());
452 }
453
454 #[test]
455 fn options_clone_works() {
456 let o = CppGenOptions {
457 namespace_prefix: Some("foo".into()),
458 include_guard_prefix: Some("FOO_".into()),
459 indent_width: 2,
460 emit_amqp_helpers: false,
461 emit_corba_traits: false,
462 };
463 let cloned = o.clone();
464 assert_eq!(cloned.indent_width, 2);
465 assert_eq!(cloned.namespace_prefix.as_deref(), Some("foo"));
466 }
467}