use std::fmt::Write;
use zerodds_idl::ast::{
Annotation, AnnotationParams, BitmaskDecl, BitsetDecl, ConstExpr, LiteralKind,
};
use crate::error::CppGenError;
const DEFAULT_BIT_BOUND: u32 = 32;
const MAX_BIT_BOUND: u32 = 64;
fn fmt_err(_e: std::fmt::Error) -> CppGenError {
CppGenError::Internal("string formatting failed".into())
}
fn underlying_type_for(bit_bound: u32) -> &'static str {
match bit_bound {
0..=8 => "uint8_t",
9..=16 => "uint16_t",
17..=32 => "uint32_t",
_ => "uint64_t",
}
}
pub(crate) fn emit_bitmask(
out: &mut String,
indent: &str,
inner: &str,
b: &BitmaskDecl,
) -> Result<(), CppGenError> {
let name = &b.name.text;
let bit_bound =
extract_int_annotation(&b.annotations, "bit_bound").unwrap_or(DEFAULT_BIT_BOUND);
if bit_bound > MAX_BIT_BOUND {
return Err(CppGenError::UnsupportedConstruct {
construct: format!("bitmask bit_bound {bit_bound} > 64"),
context: Some(name.clone()),
});
}
let underlying = underlying_type_for(bit_bound);
writeln!(out, "{indent}/** IDL `@bit_bound({bit_bound})` */").map_err(fmt_err)?;
writeln!(out, "{indent}enum class {name} : {underlying} {{").map_err(fmt_err)?;
let mut next_pos: u32 = 0;
for v in &b.values {
let pos = extract_int_annotation(&v.annotations, "position").unwrap_or(next_pos);
next_pos = pos.saturating_add(1);
writeln!(out, "{inner}{} = 1ULL << {pos},", v.name.text).map_err(fmt_err)?;
}
writeln!(out, "{indent}}};").map_err(fmt_err)?;
writeln!(out).map_err(fmt_err)?;
writeln!(
out,
"{indent}constexpr {name} operator|({name} a, {name} b) noexcept {{"
)
.map_err(fmt_err)?;
writeln!(
out,
"{inner}return static_cast<{name}>(static_cast<{underlying}>(a) | static_cast<{underlying}>(b));"
)
.map_err(fmt_err)?;
writeln!(out, "{indent}}}").map_err(fmt_err)?;
writeln!(
out,
"{indent}constexpr {name} operator&({name} a, {name} b) noexcept {{"
)
.map_err(fmt_err)?;
writeln!(
out,
"{inner}return static_cast<{name}>(static_cast<{underlying}>(a) & static_cast<{underlying}>(b));"
)
.map_err(fmt_err)?;
writeln!(out, "{indent}}}").map_err(fmt_err)?;
writeln!(
out,
"{indent}constexpr {name} operator^({name} a, {name} b) noexcept {{"
)
.map_err(fmt_err)?;
writeln!(
out,
"{inner}return static_cast<{name}>(static_cast<{underlying}>(a) ^ static_cast<{underlying}>(b));"
)
.map_err(fmt_err)?;
writeln!(out, "{indent}}}").map_err(fmt_err)?;
writeln!(
out,
"{indent}constexpr {name} operator~({name} a) noexcept {{"
)
.map_err(fmt_err)?;
writeln!(
out,
"{inner}return static_cast<{name}>(~static_cast<{underlying}>(a));"
)
.map_err(fmt_err)?;
writeln!(out, "{indent}}}").map_err(fmt_err)?;
writeln!(out).map_err(fmt_err)?;
Ok(())
}
pub(crate) fn emit_bitset(
out: &mut String,
indent: &str,
inner: &str,
b: &BitsetDecl,
) -> Result<(), CppGenError> {
let name = &b.name.text;
let mut total_width: u32 = 0;
let mut entries: Vec<(Option<&str>, u32, u32)> = Vec::new(); for f in &b.bitfields {
let width =
const_expr_to_u32(&f.spec.width).ok_or_else(|| CppGenError::UnsupportedConstruct {
construct: "bitset width must be const integer".into(),
context: Some(name.clone()),
})?;
let offset = total_width;
total_width =
total_width
.checked_add(width)
.ok_or_else(|| CppGenError::UnsupportedConstruct {
construct: "bitset total width overflow".into(),
context: Some(name.clone()),
})?;
if total_width > MAX_BIT_BOUND {
return Err(CppGenError::UnsupportedConstruct {
construct: format!("bitset total width {total_width} > {MAX_BIT_BOUND}"),
context: Some(name.clone()),
});
}
entries.push((f.name.as_ref().map(|i| i.text.as_str()), width, offset));
}
writeln!(out, "{indent}struct {name} {{").map_err(fmt_err)?;
writeln!(out, "{inner}uint64_t value{{}};").map_err(fmt_err)?;
writeln!(out).map_err(fmt_err)?;
for (field_name, width, offset) in &entries {
let Some(fname) = field_name else {
continue; };
let mask: u64 = if *width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
};
writeln!(
out,
"{inner}uint64_t {fname}() const noexcept {{ return (value >> {offset}) & 0x{mask:X}ULL; }}"
)
.map_err(fmt_err)?;
writeln!(
out,
"{inner}void {fname}(uint64_t v) noexcept {{ value = (value & ~(0x{mask:X}ULL << {offset})) | ((v & 0x{mask:X}ULL) << {offset}); }}"
)
.map_err(fmt_err)?;
}
writeln!(out, "{indent}}};").map_err(fmt_err)?;
writeln!(out).map_err(fmt_err)?;
Ok(())
}
fn const_expr_to_u32(e: &ConstExpr) -> Option<u32> {
if let ConstExpr::Literal(l) = e {
if matches!(l.kind, LiteralKind::Integer) {
return l.raw.parse::<u32>().ok();
}
}
None
}
fn extract_int_annotation(anns: &[Annotation], name: &str) -> Option<u32> {
let a = anns
.iter()
.find(|a| a.name.parts.last().map(|p| p.text.as_str()) == Some(name))?;
if let AnnotationParams::Single(ConstExpr::Literal(l)) = &a.params {
if matches!(l.kind, LiteralKind::Integer) {
return l.raw.parse::<u32>().ok();
}
}
None
}