#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![allow(
clippy::manual_pattern_char_comparison,
clippy::if_same_then_else,
clippy::collapsible_if,
clippy::useless_conversion,
clippy::approx_constant
)]
pub(crate) mod amqp;
pub(crate) mod bitset;
pub mod c_mode;
pub(crate) mod corba_traits;
pub mod dcps;
pub mod emitter;
pub mod error;
pub mod psm_cxx;
pub mod qos;
pub mod rpc;
pub mod status;
pub mod type_map;
pub(crate) mod verbatim;
pub use c_mode::{CGenOptions, generate_c_header};
pub use error::CppGenError;
pub use psm_cxx::{
emit_condition_skeleton, emit_core_basics, emit_exception_hierarchy,
emit_full_psm_cxx_skeleton, emit_listener_skeleton, emit_psm_cxx_includes,
emit_reference_value_pattern,
};
use zerodds_idl::ast::Specification;
#[derive(Debug, Clone)]
pub struct CppGenOptions {
pub namespace_prefix: Option<String>,
pub include_guard_prefix: Option<String>,
pub indent_width: usize,
pub emit_amqp_helpers: bool,
pub emit_corba_traits: bool,
}
impl Default for CppGenOptions {
fn default() -> Self {
Self {
namespace_prefix: None,
include_guard_prefix: None,
indent_width: 4,
emit_amqp_helpers: false,
emit_corba_traits: false,
}
}
}
pub(crate) const TIME_DURATION_TYPES: &[(&str, &str)] = &[
("Time_t", "DDS::Time_t"),
("Duration_t", "DDS::Duration_t"),
("Time", "DDS::Time_t"),
("Duration", "DDS::Duration_t"),
];
pub fn generate_cpp_header(
ast: &Specification,
opts: &CppGenOptions,
) -> Result<String, CppGenError> {
let mut out = emitter::emit_header(ast, opts)?;
if opts.emit_amqp_helpers {
amqp::emit_amqp_helpers(&mut out, ast)?;
}
if opts.emit_corba_traits {
corba_traits::emit_corba_traits(&mut out, ast)?;
}
Ok(out)
}
pub fn generate_cpp_header_with_corba_traits(
ast: &Specification,
opts: &CppGenOptions,
) -> Result<String, CppGenError> {
let opts = CppGenOptions {
emit_corba_traits: true,
..opts.clone()
};
generate_cpp_header(ast, &opts)
}
pub fn generate_cpp_header_with_amqp(
ast: &Specification,
opts: &CppGenOptions,
) -> Result<String, CppGenError> {
let opts = CppGenOptions {
emit_amqp_helpers: true,
..opts.clone()
};
generate_cpp_header(ast, &opts)
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic)]
use super::*;
use zerodds_idl::config::ParserConfig;
fn gen_cpp(src: &str) -> String {
let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse must succeed");
generate_cpp_header(&ast, &CppGenOptions::default()).expect("gen must succeed")
}
#[test]
fn empty_source_emits_only_preamble() {
let cpp = gen_cpp("");
assert!(cpp.contains("#pragma once"));
assert!(cpp.contains("Generated by zerodds idl-cpp"));
assert!(!cpp.contains("namespace M {"));
}
#[test]
fn empty_module_emits_namespace() {
let cpp = gen_cpp("module M {};");
assert!(cpp.contains("namespace M {"));
assert!(cpp.contains("} // namespace M"));
}
#[test]
fn three_level_modules_nest() {
let cpp = gen_cpp("module A { module B { module C {}; }; };");
assert!(cpp.contains("namespace A {"));
assert!(cpp.contains("namespace B {"));
assert!(cpp.contains("namespace C {"));
assert!(cpp.contains("} // namespace C"));
assert!(cpp.contains("} // namespace B"));
assert!(cpp.contains("} // namespace A"));
}
#[test]
fn primitive_struct_member_uses_correct_cpp_types() {
let cpp = gen_cpp(
"struct S { boolean b; octet o; short s; long l; long long ll; \
unsigned short us; unsigned long ul; unsigned long long ull; \
float f; double d; };",
);
assert!(cpp.contains("bool b_;"));
assert!(cpp.contains("uint8_t o_;"));
assert!(cpp.contains("int16_t s_;"));
assert!(cpp.contains("int32_t l_;"));
assert!(cpp.contains("int64_t ll_;"));
assert!(cpp.contains("uint16_t us_;"));
assert!(cpp.contains("uint32_t ul_;"));
assert!(cpp.contains("uint64_t ull_;"));
assert!(cpp.contains("float f_;"));
assert!(cpp.contains("double d_;"));
}
#[test]
fn string_member_requires_string_include() {
let cpp = gen_cpp("struct S { string name; };");
assert!(cpp.contains("#include <string>"));
assert!(cpp.contains("std::string name_;"));
}
#[test]
fn sequence_member_uses_vector() {
let cpp = gen_cpp("struct S { sequence<long> data; };");
assert!(cpp.contains("#include <vector>"));
assert!(cpp.contains("std::vector<int32_t> data_;"));
}
#[test]
fn array_member_uses_std_array() {
let cpp = gen_cpp("struct S { long matrix[3][4]; };");
assert!(cpp.contains("#include <array>"));
assert!(cpp.contains("std::array<std::array<int32_t, 4>, 3>"));
}
#[test]
fn enum_emits_enum_class_int32_t() {
let cpp = gen_cpp("enum Color { RED, GREEN, BLUE };");
assert!(cpp.contains("enum class Color : int32_t"));
assert!(cpp.contains("RED,"));
assert!(cpp.contains("BLUE,"));
}
#[test]
fn typedef_emits_using_alias() {
let cpp = gen_cpp("typedef long MyInt;");
assert!(cpp.contains("using MyInt = int32_t;"));
}
#[test]
fn inheritance_emits_public_base() {
let cpp = gen_cpp("struct Parent { long x; }; struct Child : Parent { long y; };");
assert!(cpp.contains("class Child : public Parent"));
}
#[test]
fn keyed_struct_marker_appears() {
let cpp = gen_cpp("struct S { @key long id; long val; };");
assert!(cpp.contains("// @key"));
}
#[test]
fn optional_member_uses_std_optional() {
let cpp = gen_cpp("struct S { @optional long maybe; };");
assert!(cpp.contains("#include <optional>"));
assert!(cpp.contains("std::optional<int32_t>"));
}
#[test]
fn exception_inherits_std_exception() {
let cpp = gen_cpp("exception NotFound { string what_; };");
assert!(cpp.contains("#include <exception>"));
assert!(cpp.contains("class NotFound : public std::exception"));
}
#[test]
fn union_uses_std_variant() {
let cpp = gen_cpp(
"union U switch (long) { case 1: long a; case 2: double b; default: octet c; };",
);
assert!(cpp.contains("#include <variant>"));
assert!(cpp.contains("std::variant<"));
assert!(cpp.contains("// case default"));
}
#[test]
fn time_t_member_maps_to_dds_time_t() {
let cpp = gen_cpp("struct S { Time_t t; };");
assert!(cpp.contains("DDS::Time_t"));
}
#[test]
fn duration_t_member_maps_to_dds_duration_t() {
let cpp = gen_cpp("struct S { Duration_t d; };");
assert!(cpp.contains("DDS::Duration_t"));
}
#[test]
fn reserved_field_name_is_rejected() {
let ast = zerodds_idl::parse("struct S { long class_field; };", &ParserConfig::default())
.expect("parse");
let res = type_map::check_identifier("class");
assert!(matches!(res, Err(CppGenError::InvalidName { .. })));
let _ = ast; }
#[test]
fn empty_source_includes_cstdint() {
let cpp = gen_cpp("");
assert!(cpp.contains("#include <cstdint>"));
}
#[test]
fn header_starts_with_generated_marker() {
let cpp = gen_cpp("");
assert!(cpp.starts_with("// Generated by zerodds idl-cpp."));
}
#[test]
fn pragma_once_appears_exactly_once() {
let cpp = gen_cpp("module M { struct S { long x; }; };");
let count = cpp.matches("#pragma once").count();
assert_eq!(count, 1);
}
#[test]
fn struct_has_default_constructor() {
let cpp = gen_cpp("struct S { long x; };");
assert!(cpp.contains("S() = default;"));
assert!(cpp.contains("~S() = default;"));
}
#[test]
fn struct_has_mutable_and_const_getter() {
let cpp = gen_cpp("struct S { long x; };");
assert!(cpp.contains("int32_t& x()"));
assert!(cpp.contains("const int32_t& x() const"));
}
#[test]
fn struct_has_setter() {
let cpp = gen_cpp("struct S { long x; };");
assert!(cpp.contains("void x(const int32_t& value)"));
}
#[test]
fn namespace_prefix_option_wraps_output() {
let ast =
zerodds_idl::parse("struct S { long x; };", &ParserConfig::default()).expect("parse");
let opts = CppGenOptions {
namespace_prefix: Some("zerodds".into()),
..Default::default()
};
let cpp = generate_cpp_header(&ast, &opts).expect("gen");
assert!(cpp.contains("namespace zerodds {"));
assert!(cpp.contains("} // namespace zerodds"));
}
#[test]
fn non_service_interface_emits_pure_virtual_class() {
let ast = zerodds_idl::parse("interface I { void op(); };", &ParserConfig::default())
.expect("parse");
let cpp = generate_cpp_header(&ast, &CppGenOptions::default()).expect("ok");
assert!(cpp.contains("class I"));
assert!(cpp.contains("virtual ~I()"));
assert!(cpp.contains("= 0;"));
}
#[test]
fn const_decl_emits_constexpr() {
let cpp = gen_cpp("const long MAX = 100;");
assert!(cpp.contains("constexpr int32_t MAX = 100;"));
}
#[test]
fn options_have_sensible_defaults() {
let o = CppGenOptions::default();
assert_eq!(o.indent_width, 4);
assert!(o.namespace_prefix.is_none());
assert!(o.include_guard_prefix.is_none());
}
#[test]
fn options_clone_works() {
let o = CppGenOptions {
namespace_prefix: Some("foo".into()),
include_guard_prefix: Some("FOO_".into()),
indent_width: 2,
emit_amqp_helpers: false,
emit_corba_traits: false,
};
let cloned = o.clone();
assert_eq!(cloned.indent_width, 2);
assert_eq!(cloned.namespace_prefix.as_deref(), Some("foo"));
}
}