zerodds-idl-rust 1.0.0-rc.1

IDL4 → Rust code generator for ZeroDDS DataTypes (impl DdsType from zerodds-dcps).
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 ZeroDDS Contributors
//! IDL `bitset` / `bitmask` → Rust.
//!
//! XTypes 1.3 §7.4.7 / IDL4 §7.4.13:
//!
//! - `bitset` ist eine packed-Bitfeld-Struktur. Wir mappen sie auf
//!   einen `pub struct Name { storage: <integer> }` mit Getter/Setter
//!   pro benamtem Bitfield. Wire-Storage richtet sich nach der
//!   Gesamtbreite (≤8 → u8, ≤16 → u16, ≤32 → u32, ≤64 → u64).
//! - `bitmask` ist eine Aufzaehlung von Bit-Positionen mit OR-baren
//!   Werten. Wir mappen sie auf `pub struct Name(u32)` (oder u8/u16/u64
//!   je nach Bit-Anzahl) mit `const`-pro-Wert und bitwise-Operations.

use zerodds_idl::ast::types::{BitValue, BitmaskDecl, BitsetDecl};

use crate::error::{Result, RustGenError};
use crate::type_map::{const_expr_as_usize, escape_keyword};

/// Emittiert eine IDL-`bitset`-Definition als Rust-`pub struct`.
pub fn emit_bitset(out: &mut String, b: &BitsetDecl) -> Result<()> {
    let total_bits = b
        .bitfields
        .iter()
        .map(|bf| const_expr_as_usize(&bf.spec.width).unwrap_or(0))
        .sum::<usize>();
    let storage_type = bitset_storage_type(total_bits);

    let name = escape_keyword(&b.name.text);

    out.push_str("/// Generated by `zerodds-idl-rust` from IDL bitset.\n");
    out.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\n");
    out.push_str(&format!("pub struct {name} {{\n"));
    out.push_str(&format!("    storage: {storage_type},\n"));
    out.push_str("}\n\n");

    out.push_str(&format!("impl {name} {{\n"));
    out.push_str(&format!(
        "    /// Konstruiert einen `{name}` mit allen Bits = 0.\n"
    ));
    out.push_str("    #[must_use]\n");
    out.push_str("    pub const fn new() -> Self {\n");
    out.push_str(&format!(
        "        Self {{ storage: 0 as {storage_type} }}\n"
    ));
    out.push_str("    }\n\n");

    out.push_str(&format!("    /// Roh-Storage als `{storage_type}`.\n"));
    out.push_str("    #[must_use]\n");
    out.push_str(&format!(
        "    pub const fn as_raw(self) -> {storage_type} {{\n"
    ));
    out.push_str("        self.storage\n");
    out.push_str("    }\n\n");

    out.push_str(&format!(
        "    /// Konstruiert aus rohem `{storage_type}`.\n"
    ));
    out.push_str("    #[must_use]\n");
    out.push_str(&format!(
        "    pub const fn from_raw(storage: {storage_type}) -> Self {{\n"
    ));
    out.push_str("        Self { storage }\n");
    out.push_str("    }\n");

    let mut offset: usize = 0;
    for bf in &b.bitfields {
        let width = const_expr_as_usize(&bf.spec.width).ok_or(RustGenError::InvalidAnnotation {
            name: "bitfield-width".to_string(),
            reason: "non-integer width",
        })?;
        if let Some(field_name) = &bf.name {
            emit_bitfield_accessors(out, field_name.text.as_str(), storage_type, offset, width);
        }
        offset += width;
    }
    out.push_str("}\n\n");

    // CdrEncode/CdrDecode: einfacher Storage-Roundtrip.
    out.push_str(&format!("impl zerodds_cdr::CdrEncode for {name} {{\n"));
    out.push_str("    fn encode(&self, w: &mut zerodds_cdr::BufferWriter) -> ::core::result::Result<(), zerodds_cdr::EncodeError> {\n");
    out.push_str(&format!(
        "        <{storage_type} as zerodds_cdr::CdrEncode>::encode(&self.storage, w)\n"
    ));
    out.push_str("    }\n");
    out.push_str("}\n\n");

    out.push_str(&format!("impl zerodds_cdr::CdrDecode for {name} {{\n"));
    out.push_str("    fn decode(r: &mut zerodds_cdr::BufferReader<'_>) -> ::core::result::Result<Self, zerodds_cdr::DecodeError> {\n");
    out.push_str(&format!(
        "        let storage = <{storage_type} as zerodds_cdr::CdrDecode>::decode(r)?;\n"
    ));
    out.push_str("        ::core::result::Result::Ok(Self { storage })\n");
    out.push_str("    }\n");
    out.push_str("}\n");

    Ok(())
}

fn emit_bitfield_accessors(
    out: &mut String,
    field_name: &str,
    storage_type: &str,
    offset: usize,
    width: usize,
) {
    let escaped_name = escape_keyword(field_name);
    let mask: u128 = if width >= 128 {
        u128::MAX
    } else {
        (1u128 << width) - 1
    };

    if width == 1 {
        // Single-Bit-Pfad: bool-Getter/Setter
        out.push_str(&format!(
            "\n    /// Getter fuer `{field_name}` (1 bit @ offset {offset}).\n"
        ));
        out.push_str("    #[must_use]\n");
        out.push_str(&format!(
            "    pub const fn {escaped_name}(self) -> bool {{\n"
        ));
        out.push_str(&format!("        ((self.storage >> {offset}) & 1) != 0\n"));
        out.push_str("    }\n");

        out.push_str(&format!(
            "\n    /// Setter fuer `{field_name}` (1 bit @ offset {offset}).\n"
        ));
        out.push_str(&format!(
            "    pub fn set_{escaped_name}(&mut self, value: bool) {{\n"
        ));
        out.push_str(&format!(
            "        let mask: {storage_type} = (1 as {storage_type}) << {offset};\n"
        ));
        out.push_str("        if value {\n");
        out.push_str("            self.storage |= mask;\n");
        out.push_str("        } else {\n");
        out.push_str("            self.storage &= !mask;\n");
        out.push_str("        }\n");
        out.push_str("    }\n");
    } else {
        // Multi-Bit-Pfad: Integer-Getter/Setter
        out.push_str(&format!(
            "\n    /// Getter fuer `{field_name}` ({width} bits @ offset {offset}).\n"
        ));
        out.push_str("    #[must_use]\n");
        out.push_str(&format!(
            "    pub const fn {escaped_name}(self) -> {storage_type} {{\n"
        ));
        out.push_str(&format!(
            "        (self.storage >> {offset}) & ({mask} as {storage_type})\n"
        ));
        out.push_str("    }\n");

        out.push_str(&format!(
            "\n    /// Setter fuer `{field_name}` ({width} bits @ offset {offset}).\n"
        ));
        out.push_str(&format!(
            "    pub fn set_{escaped_name}(&mut self, value: {storage_type}) {{\n"
        ));
        out.push_str(&format!(
            "        let mask: {storage_type} = ({mask} as {storage_type}) << {offset};\n"
        ));
        out.push_str("        self.storage = (self.storage & !mask) ");
        out.push_str(&format!(
            "| ((value & ({mask} as {storage_type})) << {offset});\n"
        ));
        out.push_str("    }\n");
    }
    let _ = width;
}

fn bitset_storage_type(total_bits: usize) -> &'static str {
    match total_bits {
        0..=8 => "u8",
        9..=16 => "u16",
        17..=32 => "u32",
        _ => "u64",
    }
}

/// Emittiert eine IDL-`bitmask`-Definition als Rust-Wrapper-Type
/// mit `const`-Werten + bitwise-Ops.
pub fn emit_bitmask(out: &mut String, m: &BitmaskDecl) -> Result<()> {
    let bit_count = m.values.len();
    let storage_type = bitset_storage_type(bit_count);
    let name = escape_keyword(&m.name.text);

    out.push_str("/// Generated by `zerodds-idl-rust` from IDL bitmask.\n");
    out.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\n");
    out.push_str(&format!("pub struct {name}({storage_type});\n\n"));

    out.push_str(&format!("impl {name} {{\n"));
    for (idx, value) in m.values.iter().enumerate() {
        emit_bitmask_value(out, value, storage_type, idx);
    }

    out.push_str("\n    /// Konstruiert eine leere Bitmaske (alle Bits = 0).\n");
    out.push_str("    #[must_use]\n");
    out.push_str("    pub const fn empty() -> Self {\n");
    out.push_str(&format!("        Self(0 as {storage_type})\n"));
    out.push_str("    }\n");

    out.push_str(&format!("\n    /// Roh-Storage als `{storage_type}`.\n"));
    out.push_str("    #[must_use]\n");
    out.push_str(&format!(
        "    pub const fn bits(self) -> {storage_type} {{\n"
    ));
    out.push_str("        self.0\n");
    out.push_str("    }\n");

    out.push_str("\n    /// Prueft ob alle Bits in `other` gesetzt sind.\n");
    out.push_str("    #[must_use]\n");
    out.push_str("    pub const fn contains(self, other: Self) -> bool {\n");
    out.push_str("        (self.0 & other.0) == other.0\n");
    out.push_str("    }\n");
    out.push_str("}\n\n");

    // Bitwise-Operations.
    for op in &["BitOr", "BitAnd", "BitXor"] {
        out.push_str(&format!(
            "impl ::core::ops::{op} for {name} {{\n    type Output = Self;\n    fn {fn_name}(self, rhs: Self) -> Self {{\n        Self(self.0 {op_sym} rhs.0)\n    }}\n}}\n\n",
            fn_name = op.to_lowercase(),
            op_sym = match *op {
                "BitOr" => "|",
                "BitAnd" => "&",
                "BitXor" => "^",
                _ => "?",
            }
        ));
    }

    // CdrEncode/CdrDecode
    out.push_str(&format!("impl zerodds_cdr::CdrEncode for {name} {{\n"));
    out.push_str("    fn encode(&self, w: &mut zerodds_cdr::BufferWriter) -> ::core::result::Result<(), zerodds_cdr::EncodeError> {\n");
    out.push_str(&format!(
        "        <{storage_type} as zerodds_cdr::CdrEncode>::encode(&self.0, w)\n"
    ));
    out.push_str("    }\n");
    out.push_str("}\n\n");

    out.push_str(&format!("impl zerodds_cdr::CdrDecode for {name} {{\n"));
    out.push_str("    fn decode(r: &mut zerodds_cdr::BufferReader<'_>) -> ::core::result::Result<Self, zerodds_cdr::DecodeError> {\n");
    out.push_str(&format!(
        "        let v = <{storage_type} as zerodds_cdr::CdrDecode>::decode(r)?;\n"
    ));
    out.push_str("        ::core::result::Result::Ok(Self(v))\n");
    out.push_str("    }\n");
    out.push_str("}\n");

    Ok(())
}

fn emit_bitmask_value(out: &mut String, value: &BitValue, storage_type: &str, position: usize) {
    let const_name = value.name.text.to_uppercase();
    out.push_str(&format!(
        "    /// Bit-Wert `{name}` (Position {position}).\n",
        name = value.name.text
    ));
    out.push_str(&format!(
        "    pub const {const_name}: Self = Self((1 as {storage_type}) << {position});\n",
    ));
}