#![allow(clippy::module_name_repetitions)]
use core::fmt::Write as _;
use zerodds_idl::ast::{
Annotation, AnnotationParams, ConstExpr, ConstrTypeDecl, Definition, FloatingType, IntegerType,
LiteralKind, ModuleDef, PrimitiveType, Specification, StructDef, TypeDecl, TypeSpec,
};
use crate::error::CppGenError;
fn unsupported(kind: &'static str) -> CppGenError {
CppGenError::UnsupportedConstruct {
construct: kind.to_string(),
context: None,
}
}
#[derive(Debug, Clone, Default)]
pub struct CGenOptions {
pub include_guard: Option<String>,
pub file_header: Option<String>,
}
pub fn generate_c_header(ast: &Specification, opts: &CGenOptions) -> Result<String, CppGenError> {
let mut ctx = Ctx::new(opts);
ctx.emit_preamble();
ctx.walk_definitions(&ast.definitions, &[])?;
ctx.emit_postamble();
Ok(ctx.out)
}
struct Ctx<'a> {
out: String,
opts: &'a CGenOptions,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Extensibility {
Final,
Appendable,
Mutable,
}
impl<'a> Ctx<'a> {
fn new(opts: &'a CGenOptions) -> Self {
Self {
out: String::new(),
opts,
}
}
fn emit_preamble(&mut self) {
let guard = self
.opts
.include_guard
.clone()
.unwrap_or_else(|| "ZERODDS_GENERATED_H".to_string());
if let Some(h) = &self.opts.file_header {
for line in h.lines() {
let _ = writeln!(self.out, "/* {line} */");
}
} else {
let _ = writeln!(
self.out,
"/* Generated by zerodds idl-cpp c_mode. Do not edit. */"
);
}
let _ = writeln!(self.out, "#ifndef {guard}");
let _ = writeln!(self.out, "#define {guard}");
let _ = writeln!(self.out, "#include <stddef.h>");
let _ = writeln!(self.out, "#include <stdint.h>");
let _ = writeln!(self.out, "#include <string.h>");
let _ = writeln!(self.out, "#include <stdlib.h>");
let _ = writeln!(self.out, "#include \"zerodds.h\"");
let _ = writeln!(self.out, "#include \"zerodds_xcdr2.h\"");
let _ = writeln!(self.out, "#ifdef __cplusplus");
let _ = writeln!(self.out, "extern \"C\" {{");
let _ = writeln!(self.out, "#endif");
let _ = writeln!(self.out);
}
fn emit_postamble(&mut self) {
let _ = writeln!(self.out, "#ifdef __cplusplus");
let _ = writeln!(self.out, "}}");
let _ = writeln!(self.out, "#endif");
let _ = writeln!(self.out, "#endif");
}
fn walk_definitions(
&mut self,
defs: &[Definition],
scope: &[String],
) -> Result<(), CppGenError> {
for d in defs {
match d {
Definition::Module(m) => self.walk_module(m, scope)?,
Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(sd))) => {
if let zerodds_idl::ast::StructDcl::Def(def) = sd {
self.emit_struct(def, scope)?;
}
}
Definition::Type(_) => {
return Err(unsupported("non-struct type"));
}
Definition::Const(_)
| Definition::Annotation(_)
| Definition::TypeId(_)
| Definition::TypePrefix(_)
| Definition::Import(_) => {
}
_ => {
return Err(unsupported("non-struct definition"));
}
}
}
Ok(())
}
fn walk_module(&mut self, m: &ModuleDef, scope: &[String]) -> Result<(), CppGenError> {
let mut new_scope = scope.to_vec();
new_scope.push(m.name.text.clone());
self.walk_definitions(&m.definitions, &new_scope)
}
fn emit_struct(&mut self, def: &StructDef, scope: &[String]) -> Result<(), CppGenError> {
let ext = extensibility_of(&def.annotations);
let c_name = c_identifier(scope, &def.name.text);
let dds_name = dds_type_name(scope, &def.name.text);
let _ = writeln!(self.out, "typedef struct {c_name}_s {{");
for member in &def.members {
for decl in &member.declarators {
let m_name = decl.name();
let c_type = c_type_for(&member.type_spec)?;
let _ = writeln!(self.out, " {c_type} {field};", field = m_name.text);
}
}
if def.members.is_empty() {
let _ = writeln!(self.out, " uint8_t _zerodds_empty;");
}
let _ = writeln!(self.out, "}} {c_name}_t;");
let _ = writeln!(self.out);
let _ = writeln!(
self.out,
"static int {c_name}_encode(const void* sample, uint8_t* out_buf, size_t out_cap, size_t* out_len);"
);
let _ = writeln!(
self.out,
"static int {c_name}_decode(const uint8_t* buf, size_t len, void* out_sample);"
);
let _ = writeln!(self.out, "static void {c_name}_sample_free(void* sample);");
let has_key = struct_has_key(def);
if has_key {
let _ = writeln!(
self.out,
"static int {c_name}_key_hash(const void* sample, uint8_t out_hash[16]);"
);
}
let _ = writeln!(self.out);
let _ = writeln!(
self.out,
"static const char {c_name}_type_name[] = \"{dds_name}\";"
);
let _ = writeln!(
self.out,
"static const zerodds_typesupport_t {c_name}_typesupport = {{"
);
let _ = writeln!(self.out, " .type_hash = {{0}},");
let _ = writeln!(self.out, " .type_name = {c_name}_type_name,");
let _ = writeln!(self.out, " .is_keyed = {},", if has_key { 1 } else { 0 });
let _ = writeln!(self.out, " .extensibility = {},", ext.as_u8());
let _ = writeln!(self.out, " ._reserved = {{0}},");
let _ = writeln!(self.out, " .encode = {c_name}_encode,");
let _ = writeln!(self.out, " .decode = {c_name}_decode,");
if has_key {
let _ = writeln!(self.out, " .key_hash = {c_name}_key_hash,");
} else {
let _ = writeln!(self.out, " .key_hash = NULL,");
}
let _ = writeln!(self.out, " .sample_free = {c_name}_sample_free,");
let _ = writeln!(self.out, "}};");
let _ = writeln!(self.out);
self.emit_encode_body(&c_name, def, ext)?;
self.emit_decode_body(&c_name, def, ext)?;
self.emit_free_body(&c_name, def);
if has_key {
self.emit_key_hash_body(&c_name, def);
}
Ok(())
}
fn emit_encode_body(
&mut self,
c_name: &str,
def: &StructDef,
ext: Extensibility,
) -> Result<(), CppGenError> {
let _ = writeln!(
self.out,
"static int {c_name}_encode(const void* sample, uint8_t* out_buf, size_t out_cap, size_t* out_len) {{"
);
let _ = writeln!(
self.out,
" const {c_name}_t* s = (const {c_name}_t*)sample;"
);
let _ = writeln!(self.out, " (void)s;");
let _ = writeln!(
self.out,
" /* Two-pass: Buffer wachsen lassen, dann kopieren. */"
);
let _ = writeln!(self.out, " uint8_t* w_buf = NULL;");
let _ = writeln!(self.out, " size_t w_len = 0;");
let _ = writeln!(self.out, " size_t w_cap = 0;");
let _ = writeln!(
self.out,
" if (out_buf == NULL && out_cap > 0) goto fail;"
);
match ext {
Extensibility::Final => {
self.emit_struct_body_writes(def)?;
}
Extensibility::Appendable => {
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + 4) != 0) goto fail;"
);
let _ = writeln!(self.out, " size_t dheader_pos = w_len;");
let _ = writeln!(self.out, " w_len += 4;");
let _ = writeln!(self.out, " size_t body_start = w_len;");
self.emit_struct_body_writes(def)?;
let _ = writeln!(
self.out,
" uint32_t dheader_len = (uint32_t)(w_len - body_start);"
);
let _ = writeln!(
self.out,
" zerodds_xcdr2_c_put_u32_at(w_buf, dheader_pos, dheader_len);"
);
}
Extensibility::Mutable => {
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + 4) != 0) goto fail;"
);
let _ = writeln!(self.out, " size_t dheader_pos = w_len;");
let _ = writeln!(self.out, " w_len += 4;");
let _ = writeln!(self.out, " size_t mut_body_start = w_len;");
self.emit_mutable_member_writes(def)?;
let _ = writeln!(
self.out,
" uint32_t dheader_len = (uint32_t)(w_len - mut_body_start);"
);
let _ = writeln!(
self.out,
" zerodds_xcdr2_c_put_u32_at(w_buf, dheader_pos, dheader_len);"
);
}
}
let _ = writeln!(self.out, " if (out_len) *out_len = w_len;");
let _ = writeln!(
self.out,
" if (out_buf == NULL || out_cap < w_len) {{ free(w_buf); return -13; }}"
);
let _ = writeln!(
self.out,
" if (w_len > 0) memcpy(out_buf, w_buf, w_len);"
);
let _ = writeln!(self.out, " free(w_buf);");
let _ = writeln!(self.out, " return 0;");
let _ = writeln!(self.out, "fail:");
let _ = writeln!(self.out, " free(w_buf);");
let _ = writeln!(self.out, " return -1;");
let _ = writeln!(self.out, "}}");
let _ = writeln!(self.out);
Ok(())
}
fn emit_struct_body_writes(&mut self, def: &StructDef) -> Result<(), CppGenError> {
for member in &def.members {
for decl in &member.declarators {
let f = &decl.name().text;
self.emit_member_write(&format!("s->{f}"), &member.type_spec)?;
}
}
Ok(())
}
fn emit_member_write(&mut self, var: &str, type_spec: &TypeSpec) -> Result<(), CppGenError> {
match type_spec {
TypeSpec::Primitive(p) => self.emit_primitive_write(var, *p),
TypeSpec::String(st) => {
if st.wide {
return Err(unsupported("wstring"));
}
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_write_string(&w_buf, &w_len, &w_cap, {var}) != 0) goto fail;"
);
Ok(())
}
TypeSpec::Sequence(seq) => self.emit_sequence_write(var, &seq.elem),
TypeSpec::Scoped(_) => Err(unsupported("nested struct reference")),
TypeSpec::Fixed(_) => Err(unsupported("fixed")),
TypeSpec::Map(_) => Err(unsupported("map")),
TypeSpec::Any => Err(unsupported("any")),
}
}
fn emit_primitive_write(&mut self, var: &str, p: PrimitiveType) -> Result<(), CppGenError> {
let helper = match p {
PrimitiveType::Boolean | PrimitiveType::Octet => "u8",
PrimitiveType::Char => "u8",
PrimitiveType::WideChar => "u16",
PrimitiveType::Integer(IntegerType::Short)
| PrimitiveType::Integer(IntegerType::Int16) => "i16",
PrimitiveType::Integer(IntegerType::UShort)
| PrimitiveType::Integer(IntegerType::UInt16) => "u16",
PrimitiveType::Integer(IntegerType::Long)
| PrimitiveType::Integer(IntegerType::Int32) => "i32",
PrimitiveType::Integer(IntegerType::ULong)
| PrimitiveType::Integer(IntegerType::UInt32) => "u32",
PrimitiveType::Integer(IntegerType::LongLong)
| PrimitiveType::Integer(IntegerType::Int64) => "i64",
PrimitiveType::Integer(IntegerType::ULongLong)
| PrimitiveType::Integer(IntegerType::UInt64) => "u64",
PrimitiveType::Integer(IntegerType::Int8) => "i8",
PrimitiveType::Integer(IntegerType::UInt8) => "u8",
PrimitiveType::Floating(FloatingType::Float) => "f32",
PrimitiveType::Floating(FloatingType::Double) => "f64",
PrimitiveType::Floating(FloatingType::LongDouble) => {
return Err(unsupported("long double"));
}
};
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_write_{helper}(&w_buf, &w_len, &w_cap, {var}) != 0) goto fail;"
);
Ok(())
}
fn emit_sequence_write(&mut self, var: &str, elem: &TypeSpec) -> Result<(), CppGenError> {
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, ({var}).len) != 0) goto fail;"
);
let _ = writeln!(
self.out,
" for (uint32_t i = 0; i < ({var}).len; ++i) {{"
);
match elem {
TypeSpec::Primitive(p) => {
self.emit_primitive_write(&format!("({var}).elems[i]"), *p)?
}
TypeSpec::String(st) => {
if st.wide {
return Err(unsupported("wstring"));
}
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_write_string(&w_buf, &w_len, &w_cap, ({var}).elems[i]) != 0) goto fail;"
);
}
_ => {
return Err(unsupported("non-primitive sequence element"));
}
}
let _ = writeln!(self.out, " }}");
Ok(())
}
fn emit_mutable_member_writes(&mut self, def: &StructDef) -> Result<(), CppGenError> {
for member in &def.members {
let id = id_annotation(&member.annotations)
.ok_or_else(|| unsupported("@mutable member without @id(N)"))?;
for decl in &member.declarators {
let f = &decl.name().text;
let _ = writeln!(self.out, " {{");
let _ = writeln!(self.out, " uint8_t* m_buf = NULL;");
let _ = writeln!(self.out, " size_t m_len = 0;");
let _ = writeln!(self.out, " size_t m_cap = 0;");
self.emit_member_write_to_named_buffer(
&format!("s->{f}"),
&member.type_spec,
"m_buf",
"m_len",
"m_cap",
)?;
let emheader: u32 = (4u32 << 28) | id;
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, 0x{emheader:08X}u) != 0) {{ free(m_buf); goto fail; }}"
);
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_write_u32(&w_buf, &w_len, &w_cap, (uint32_t)m_len) != 0) {{ free(m_buf); goto fail; }}"
);
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_grow(&w_buf, &w_cap, w_len + m_len) != 0) {{ free(m_buf); goto fail; }}"
);
let _ = writeln!(
self.out,
" if (m_len > 0) memcpy(w_buf + w_len, m_buf, m_len);"
);
let _ = writeln!(self.out, " w_len += m_len;");
let _ = writeln!(self.out, " free(m_buf);");
let _ = writeln!(self.out, " }}");
}
}
Ok(())
}
fn emit_member_write_to_named_buffer(
&mut self,
var: &str,
type_spec: &TypeSpec,
buf: &str,
len: &str,
cap: &str,
) -> Result<(), CppGenError> {
match type_spec {
TypeSpec::Primitive(p) => {
let helper = primitive_helper(*p)?;
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_write_{helper}(&{buf}, &{len}, &{cap}, {var}) != 0) {{ free({buf}); goto fail; }}"
);
Ok(())
}
TypeSpec::String(st) => {
if st.wide {
return Err(unsupported("wstring"));
}
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_write_string(&{buf}, &{len}, &{cap}, {var}) != 0) {{ free({buf}); goto fail; }}"
);
Ok(())
}
_ => Err(unsupported("complex type in @mutable member")),
}
}
fn emit_decode_body(
&mut self,
c_name: &str,
def: &StructDef,
ext: Extensibility,
) -> Result<(), CppGenError> {
let _ = writeln!(
self.out,
"static int {c_name}_decode(const uint8_t* buf, size_t len, void* out_sample) {{"
);
let _ = writeln!(self.out, " {c_name}_t* s = ({c_name}_t*)out_sample;");
let _ = writeln!(self.out, " size_t pos = 0;");
match ext {
Extensibility::Final => {
self.emit_struct_body_reads(def)?;
}
Extensibility::Appendable => {
let _ = writeln!(self.out, " uint32_t dheader = 0;");
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &dheader) != 0) return -7;"
);
let _ = writeln!(self.out, " size_t body_end = pos + dheader;");
let _ = writeln!(self.out, " if (body_end > len) return -7;");
self.emit_struct_body_reads(def)?;
let _ = writeln!(self.out, " pos = body_end;");
}
Extensibility::Mutable => {
let _ = writeln!(self.out, " uint32_t dheader = 0;");
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &dheader) != 0) return -7;"
);
let _ = writeln!(self.out, " size_t body_end = pos + dheader;");
let _ = writeln!(self.out, " if (body_end > len) return -7;");
let _ = writeln!(self.out, " while (pos < body_end) {{");
let _ = writeln!(self.out, " uint32_t emheader = 0;");
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &emheader) != 0) return -7;"
);
let _ = writeln!(self.out, " uint32_t mid = emheader & 0x0FFFFFFFu;");
let _ = writeln!(self.out, " uint32_t lc = (emheader >> 28) & 0x7u;");
let _ = writeln!(self.out, " uint32_t nextint = 0;");
let _ = writeln!(self.out, " size_t body_len = 0;");
let _ = writeln!(
self.out,
" if (lc == 0) body_len = 1; else if (lc == 1) body_len = 2; else if (lc == 2) body_len = 4; else if (lc == 3) body_len = 8; else {{"
);
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &nextint) != 0) return -7;"
);
let _ = writeln!(self.out, " body_len = nextint;");
let _ = writeln!(self.out, " }}");
let _ = writeln!(
self.out,
" if (pos + body_len > body_end) return -7;"
);
self.emit_mutable_member_dispatch(def)?;
let _ = writeln!(self.out, " }}");
}
}
let _ = writeln!(self.out, " (void)s;");
let _ = writeln!(self.out, " return 0;");
let _ = writeln!(self.out, "}}");
let _ = writeln!(self.out);
Ok(())
}
fn emit_struct_body_reads(&mut self, def: &StructDef) -> Result<(), CppGenError> {
for member in &def.members {
for decl in &member.declarators {
let f = &decl.name().text;
self.emit_member_read(&format!("s->{f}"), &member.type_spec)?;
}
}
Ok(())
}
fn emit_member_read(&mut self, var: &str, type_spec: &TypeSpec) -> Result<(), CppGenError> {
match type_spec {
TypeSpec::Primitive(p) => {
let helper = primitive_helper(*p)?;
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_{helper}(buf, len, &pos, &({var})) != 0) return -7;"
);
Ok(())
}
TypeSpec::String(st) => {
if st.wide {
return Err(unsupported("wstring"));
}
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_string(buf, len, &pos, &({var})) != 0) return -7;"
);
Ok(())
}
TypeSpec::Sequence(seq) => self.emit_sequence_read(var, &seq.elem),
_ => Err(unsupported("complex member read")),
}
}
fn emit_sequence_read(&mut self, var: &str, elem: &TypeSpec) -> Result<(), CppGenError> {
let _ = writeln!(self.out, " {{");
let _ = writeln!(self.out, " uint32_t seq_len = 0;");
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_u32(buf, len, &pos, &seq_len) != 0) return -7;"
);
let elem_c = c_type_for(elem)?;
let _ = writeln!(self.out, " ({var}).len = seq_len;");
let _ = writeln!(
self.out,
" ({var}).elems = ({elem_c}*)calloc(seq_len ? seq_len : 1, sizeof({elem_c}));"
);
let _ = writeln!(
self.out,
" if (({var}).elems == NULL && seq_len > 0) return -7;"
);
let _ = writeln!(
self.out,
" for (uint32_t i = 0; i < seq_len; ++i) {{"
);
match elem {
TypeSpec::Primitive(p) => {
let helper = primitive_helper(*p)?;
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_{helper}(buf, len, &pos, &({var}).elems[i]) != 0) return -7;"
);
}
TypeSpec::String(st) => {
if st.wide {
return Err(unsupported("wstring"));
}
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_read_string(buf, len, &pos, &({var}).elems[i]) != 0) return -7;"
);
}
_ => {
return Err(unsupported("complex sequence element read"));
}
}
let _ = writeln!(self.out, " }}");
let _ = writeln!(self.out, " }}");
Ok(())
}
fn emit_mutable_member_dispatch(&mut self, def: &StructDef) -> Result<(), CppGenError> {
let _ = writeln!(self.out, " switch (mid) {{");
for member in &def.members {
let Some(id) = id_annotation(&member.annotations) else {
return Err(unsupported("@mutable member without @id(N)"));
};
for decl in &member.declarators {
let f = &decl.name().text;
let _ = writeln!(self.out, " case {id}: {{");
self.emit_member_read(&format!("s->{f}"), &member.type_spec)?;
let _ = writeln!(self.out, " break;");
let _ = writeln!(self.out, " }}");
}
}
let _ = writeln!(self.out, " default: pos += body_len; break;");
let _ = writeln!(self.out, " }}");
Ok(())
}
fn emit_free_body(&mut self, c_name: &str, def: &StructDef) {
let _ = writeln!(
self.out,
"static void {c_name}_sample_free(void* sample) {{"
);
let _ = writeln!(self.out, " if (sample == NULL) return;");
let _ = writeln!(self.out, " {c_name}_t* s = ({c_name}_t*)sample;");
let _ = writeln!(self.out, " (void)s;");
for member in &def.members {
for decl in &member.declarators {
let f = &decl.name().text;
match &member.type_spec {
TypeSpec::String(_) => {
let _ = writeln!(self.out, " free(s->{f}); s->{f} = NULL;");
}
TypeSpec::Sequence(seq) => match seq.elem.as_ref() {
TypeSpec::String(_) => {
let _ = writeln!(
self.out,
" for (uint32_t i = 0; i < s->{f}.len; ++i) free(s->{f}.elems[i]);"
);
let _ = writeln!(
self.out,
" free(s->{f}.elems); s->{f}.elems = NULL; s->{f}.len = 0;"
);
}
_ => {
let _ = writeln!(
self.out,
" free(s->{f}.elems); s->{f}.elems = NULL; s->{f}.len = 0;"
);
}
},
_ => {}
}
}
}
let _ = writeln!(self.out, "}}");
let _ = writeln!(self.out);
}
fn emit_key_hash_body(&mut self, c_name: &str, def: &StructDef) {
let _ = writeln!(
self.out,
"static int {c_name}_key_hash(const void* sample, uint8_t out_hash[16]) {{"
);
let _ = writeln!(
self.out,
" const {c_name}_t* s = (const {c_name}_t*)sample;"
);
let _ = writeln!(self.out, " uint8_t* kh_buf = NULL;");
let _ = writeln!(self.out, " size_t kh_len = 0;");
let _ = writeln!(self.out, " size_t kh_cap = 0;");
for member in &def.members {
if !is_key(&member.annotations) {
continue;
}
for decl in &member.declarators {
let f = &decl.name().text;
match &member.type_spec {
TypeSpec::Primitive(p) => {
let helper = match primitive_helper(*p) {
Ok(h) => h,
Err(_) => continue,
};
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_kh_write_{helper}(&kh_buf, &kh_len, &kh_cap, s->{f}) != 0) {{ free(kh_buf); return -1; }}"
);
}
TypeSpec::String(_) => {
let _ = writeln!(
self.out,
" if (zerodds_xcdr2_c_kh_write_string(&kh_buf, &kh_len, &kh_cap, s->{f}) != 0) {{ free(kh_buf); return -1; }}"
);
}
_ => {}
}
}
}
let _ = writeln!(
self.out,
" zerodds_xcdr2_c_compute_key_hash(kh_buf, kh_len, /*max_size_static=*/0, out_hash);"
);
let _ = writeln!(self.out, " free(kh_buf);");
let _ = writeln!(self.out, " return 0;");
let _ = writeln!(self.out, "}}");
let _ = writeln!(self.out);
}
}
fn dds_type_name(scope: &[String], name: &str) -> String {
if scope.is_empty() {
name.to_string()
} else {
let mut s = scope.join("::");
s.push_str("::");
s.push_str(name);
s
}
}
fn c_identifier(scope: &[String], name: &str) -> String {
if scope.is_empty() {
name.to_string()
} else {
let mut s = scope.join("_");
s.push('_');
s.push_str(name);
s
}
}
impl Extensibility {
fn as_u8(self) -> u8 {
match self {
Self::Final => 0,
Self::Appendable => 1,
Self::Mutable => 2,
}
}
}
fn extensibility_of(annotations: &[Annotation]) -> Extensibility {
for a in annotations {
if let Some(name) = a.name.parts.last() {
match name.text.as_str() {
"final" | "Final" => return Extensibility::Final,
"appendable" | "Appendable" => return Extensibility::Appendable,
"mutable" | "Mutable" => return Extensibility::Mutable,
_ => {}
}
}
}
Extensibility::Appendable
}
fn is_key(annotations: &[Annotation]) -> bool {
annotations.iter().any(|a| {
a.name
.parts
.last()
.is_some_and(|p| p.text == "key" || p.text == "Key")
})
}
fn struct_has_key(def: &StructDef) -> bool {
def.members.iter().any(|m| is_key(&m.annotations))
}
fn id_annotation(annotations: &[Annotation]) -> Option<u32> {
for a in annotations {
let last = a.name.parts.last()?;
if last.text != "id" && last.text != "Id" {
continue;
}
if let AnnotationParams::Single(ConstExpr::Literal(lit)) = &a.params {
if lit.kind == LiteralKind::Integer {
if let Ok(v) = lit.raw.parse::<u32>() {
return Some(v);
}
}
}
}
None
}
fn c_type_for(type_spec: &TypeSpec) -> Result<String, CppGenError> {
let s = match type_spec {
TypeSpec::Primitive(p) => match p {
PrimitiveType::Boolean => "uint8_t",
PrimitiveType::Octet => "uint8_t",
PrimitiveType::Char => "char",
PrimitiveType::WideChar => "uint16_t",
PrimitiveType::Integer(IntegerType::Short)
| PrimitiveType::Integer(IntegerType::Int16) => "int16_t",
PrimitiveType::Integer(IntegerType::UShort)
| PrimitiveType::Integer(IntegerType::UInt16) => "uint16_t",
PrimitiveType::Integer(IntegerType::Long)
| PrimitiveType::Integer(IntegerType::Int32) => "int32_t",
PrimitiveType::Integer(IntegerType::ULong)
| PrimitiveType::Integer(IntegerType::UInt32) => "uint32_t",
PrimitiveType::Integer(IntegerType::LongLong)
| PrimitiveType::Integer(IntegerType::Int64) => "int64_t",
PrimitiveType::Integer(IntegerType::ULongLong)
| PrimitiveType::Integer(IntegerType::UInt64) => "uint64_t",
PrimitiveType::Integer(IntegerType::Int8) => "int8_t",
PrimitiveType::Integer(IntegerType::UInt8) => "uint8_t",
PrimitiveType::Floating(FloatingType::Float) => "float",
PrimitiveType::Floating(FloatingType::Double) => "double",
PrimitiveType::Floating(FloatingType::LongDouble) => {
return Err(unsupported("long double"));
}
},
TypeSpec::String(st) => {
if st.wide {
return Err(unsupported("wstring"));
}
"char*"
}
TypeSpec::Sequence(seq) => {
let elem = c_type_for(&seq.elem)?;
return Ok(format!("struct {{ uint32_t len; {elem}* elems; }}"));
}
_ => {
return Err(unsupported("non-primitive type in field"));
}
};
Ok(s.to_string())
}
fn primitive_helper(p: PrimitiveType) -> Result<&'static str, CppGenError> {
Ok(match p {
PrimitiveType::Boolean | PrimitiveType::Octet => "u8",
PrimitiveType::Char => "u8",
PrimitiveType::WideChar => "u16",
PrimitiveType::Integer(IntegerType::Short) | PrimitiveType::Integer(IntegerType::Int16) => {
"i16"
}
PrimitiveType::Integer(IntegerType::UShort)
| PrimitiveType::Integer(IntegerType::UInt16) => "u16",
PrimitiveType::Integer(IntegerType::Long) | PrimitiveType::Integer(IntegerType::Int32) => {
"i32"
}
PrimitiveType::Integer(IntegerType::ULong)
| PrimitiveType::Integer(IntegerType::UInt32) => "u32",
PrimitiveType::Integer(IntegerType::LongLong)
| PrimitiveType::Integer(IntegerType::Int64) => "i64",
PrimitiveType::Integer(IntegerType::ULongLong)
| PrimitiveType::Integer(IntegerType::UInt64) => "u64",
PrimitiveType::Integer(IntegerType::Int8) => "i8",
PrimitiveType::Integer(IntegerType::UInt8) => "u8",
PrimitiveType::Floating(FloatingType::Float) => "f32",
PrimitiveType::Floating(FloatingType::Double) => "f64",
PrimitiveType::Floating(FloatingType::LongDouble) => {
return Err(unsupported("long double"));
}
})
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use super::*;
use zerodds_idl::config::ParserConfig;
fn gen_c(src: &str) -> String {
let ast = zerodds_idl::parse(src, &ParserConfig::default()).expect("parse ok");
generate_c_header(&ast, &CGenOptions::default()).expect("c-gen ok")
}
#[test]
fn empty_final_struct_emits_typedef_and_typesupport() {
let h = gen_c("@final struct Empty {};");
assert!(h.contains("typedef struct Empty_s"));
assert!(h.contains("Empty_typesupport"));
assert!(h.contains(".extensibility = 0"));
}
#[test]
fn primitive_struct_maps_types() {
let h = gen_c("@final struct Point { long x; long y; };");
assert!(h.contains("int32_t x;"));
assert!(h.contains("int32_t y;"));
assert!(h.contains("\"Point\""));
}
#[test]
fn nested_module_yields_scoped_type_name() {
let h = gen_c("module Outer { module Inner { @final struct S { long x; }; }; };");
assert!(h.contains("typedef struct Outer_Inner_S_s"));
assert!(h.contains("\"Outer::Inner::S\""));
}
#[test]
fn appendable_default_when_no_annotation() {
let h = gen_c("struct V { long a; long b; };");
assert!(h.contains(".extensibility = 1"));
}
#[test]
fn mutable_struct_marks_extensibility() {
let h = gen_c("@mutable struct M { @id(1) long a; };");
assert!(h.contains(".extensibility = 2"));
}
#[test]
fn key_member_sets_is_keyed_and_emits_key_hash() {
let h = gen_c("@final struct Sensor { @key long id; double value; };");
assert!(h.contains(".is_keyed = 1"));
assert!(h.contains("Sensor_key_hash"));
}
}