use core::fmt::Write as _;
use zerodds_ast::{Ast, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::Id;
use zerodds_wire_codec::encode::ProbeError;
use crate::contract;
use crate::fill_rust::{rust_path, upper_camel};
use crate::target_keywords::rust as rust_identifier;
pub fn dump_hdds(ast: &Ast, type_: Id<TypeNode>) -> Result<String, ProbeError> {
contract::reject_alias_cycle(ast)?;
let path = contract::root_path(ast, type_);
if !crate::recursion::recursive_aggregates(ast, type_).is_empty() {
return Err(unsupported(
&path,
"a recursive type, which hddsgen emits with infinite size and no decoder",
));
}
let mut out = Dump { next: 0 };
let body = out.value(ast, type_, "sample", &path)?;
Ok(format!(
"{{\n\
\x20 fn zd_json_number(value: f64) -> serde_json::Value {{\n\
\x20 match serde_json::Number::from_f64(value) {{\n\
\x20 Some(number) => serde_json::Value::Number(number),\n\
\x20 None => panic!(\n\
\x20 \"the HDDS decoder produced a non-finite float, which JSON cannot write\"\n\
\x20 ),\n\
\x20 }}\n\
\x20 }}\n\
\x20 fn zd_json_f32(value: f32) -> serde_json::Value {{ zd_json_number(f64::from(value)) }}\n\
\x20 fn zd_json_f64(value: f64) -> serde_json::Value {{ zd_json_number(value) }}\n\
\x20 {body}\n\
}}"
))
}
#[derive(Debug, Default)]
struct Dump {
next: usize,
}
impl Dump {
fn temporary(&mut self, stem: &str) -> String {
self.next += 1;
format!("zd_{stem}_{}", self.next)
}
#[allow(clippy::too_many_lines)]
fn value(
&mut self,
ast: &Ast,
type_: Id<TypeNode>,
expr: &str,
path: &str,
) -> Result<String, ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Primitive(kind) => primitive(*kind, expr, path),
TypeKind::String { .. } => Ok(format!("serde_json::Value::String(({expr}).clone())")),
TypeKind::Enumeration { .. } => self.enumeration(ast, type_, expr),
TypeKind::Bitmask { .. } => self.bitmask(ast, type_, expr),
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the typedef target is unresolved"),
})?;
self.value(ast, target, expr, path)
}
TypeKind::Structure { .. } => self.aggregate(ast, type_, expr, path),
TypeKind::Sequence { element, .. } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the sequence element is unresolved"),
})?;
let item = self.temporary("v");
let inner =
self.value(ast, element, &format!("(*{item})"), &format!("{path}[]"))?;
Ok(format!(
"serde_json::Value::Array(({expr}).iter().map(|{item}| {inner}).collect())"
))
}
TypeKind::Array { element, dims } => {
let element = element.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the array element is unresolved"),
})?;
let counts = contract::array_dimensions(ast, dims, path)?;
self.array(ast, element, expr, path, &counts)
}
TypeKind::Union { .. } => self.union(ast, type_, expr, path),
TypeKind::Map {
key, value: entry, ..
} => {
let key_type = key.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map key type is unresolved"),
})?;
let value_type = entry.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the map value type is unresolved"),
})?;
let key_name = self.temporary("k");
let value_name = self.temporary("v");
let key_expr = self.value(
ast,
key_type,
&format!("(*{key_name})"),
&format!("{path}[].key"),
)?;
let value_expr = self.value(
ast,
value_type,
&format!("(*{value_name})"),
&format!("{path}[].value"),
)?;
Ok(format!(
"serde_json::Value::Array(({expr}).iter().map(|({key_name}, {value_name})| \
serde_json::Value::Array(vec![{key_expr}, {value_expr}])).collect())"
))
}
TypeKind::Bitset { .. } => Err(unfilled(path, "bitset")),
TypeKind::Fixed { .. } => Err(unfilled(path, "fixed")),
other => Err(unfilled(path, other.as_str())),
}
}
fn aggregate(
&mut self,
ast: &Ast,
type_: Id<TypeNode>,
expr: &str,
path: &str,
) -> Result<String, ProbeError> {
let members = rust_members(ast, type_, expr)?;
let mut out = String::from("{\n let mut zd_object = serde_json::Map::new();\n");
for member in members {
let member_path = contract::join(path, &member.name);
if member.optional {
let bound = self.temporary("some");
let inner = self.value(ast, member.type_, &format!("(*{bound})"), &member_path)?;
let _ = writeln!(
out,
" if let Some({bound}) = &{accessor} {{",
accessor = member.accessor
);
let _ = writeln!(
out,
" zd_object.insert(\"{name}\".to_string(), {inner});",
name = member.name
);
out.push_str(" }\n");
} else {
let inner = self.value(ast, member.type_, &member.accessor, &member_path)?;
let _ = writeln!(
out,
" zd_object.insert(\"{name}\".to_string(), {inner});",
name = member.name
);
}
}
out.push_str(" serde_json::Value::Object(zd_object)\n}");
Ok(out)
}
fn enumeration(
&self,
ast: &Ast,
type_: Id<TypeNode>,
expr: &str,
) -> Result<String, ProbeError> {
let path = rust_path(ast, type_);
let mut arms = String::new();
for id in ast.enumerators_of(type_) {
let node = ast.enumerator(*id).map_err(|_| ProbeError::NoLayout {
detail: format!("{expr}: an enumerator of {path} does not resolve"),
})?;
let _ = writeln!(arms, "{path}::{} => \"{}\",", node.name, node.name);
}
Ok(format!(
"serde_json::Value::String(String::from(match &{expr} {{\n{arms}}}))"
))
}
fn bitmask(&self, ast: &Ast, type_: Id<TypeNode>, expr: &str) -> Result<String, ProbeError> {
let mut out = String::from("{\n let mut zd_flags = Vec::new();\n");
for id in ast.bits_of(type_) {
let node = ast.bit_value(*id).map_err(|_| ProbeError::NoLayout {
detail: format!("{expr}: a bit of the bitmask does not resolve"),
})?;
let constant = bitmask_constant(ast, type_, &node.name);
let _ = writeln!(
out,
" if (({expr}) & {constant}) != 0 {{ \
zd_flags.push(serde_json::Value::String(\"{name}\".to_string())); }}",
name = node.name
);
}
out.push_str(" serde_json::Value::Array(zd_flags)\n}");
Ok(out)
}
fn union(
&mut self,
ast: &Ast,
type_: Id<TypeNode>,
expr: &str,
path: &str,
) -> Result<String, ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
let TypeKind::Union { discriminator, .. } = &node.kind else {
return Err(unfilled(
path,
"a union whose kind changed under the emitter",
));
};
let discriminator = discriminator
.resolved()
.ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the discriminator is unresolved"),
})?;
let mut out = String::from("{\n let mut zd_object = serde_json::Map::new();\n");
let _ = writeln!(out, " match &{expr} {{");
for case in contract::union_cases(ast, type_) {
let mut label = None;
for candidate in &case.labels {
if let Some(expr) = candidate.expr() {
label = Some(expr);
break;
}
}
let Some(label) = label else {
return Err(unfilled(
path,
"a union case selected by `default:`, whose discriminator hddsgen's \
generated enum does not carry",
));
};
let value = ast
.eval_integral(label)
.map_err(|error| ProbeError::NoLayout {
detail: format!("{path}: a case label does not evaluate: {error}"),
})?;
let printed = Self::discriminator(ast, discriminator, value, path)?;
let payload = self.temporary("p");
let member_path = contract::join(path, &case.name);
let inner = self.value(ast, case.type_, &format!("(*{payload})"), &member_path)?;
let variant = format!("{}::{}", rust_path(ast, type_), upper_camel(&case.name));
let name = case.name.clone();
let _ = writeln!(
out,
" {variant}({payload}) => {{\n\
\x20 zd_object.insert(\"_d\".to_string(), {printed});\n\
\x20 zd_object.insert(\"{name}\".to_string(), {inner});\n\
\x20 }}"
);
}
out.push_str(" }\n serde_json::Value::Object(zd_object)\n}");
Ok(out)
}
fn discriminator(
ast: &Ast,
type_: Id<TypeNode>,
value: i128,
path: &str,
) -> Result<String, ProbeError> {
let node = ast.type_(type_).map_err(|error| ProbeError::NoLayout {
detail: format!("{error}"),
})?;
match &node.kind {
TypeKind::Primitive(PrimitiveKind::Boolean) => {
Ok(format!("serde_json::Value::Bool({})", value != 0))
}
TypeKind::Primitive(PrimitiveKind::Char | PrimitiveKind::WChar) => {
let scalar = u32::try_from(value).ok().and_then(char::from_u32);
let character = scalar.ok_or_else(|| ProbeError::Mismatch {
path: path.to_string(),
expected: "a case label that is a character".to_string(),
found: format!("{value}"),
})?;
Ok(format!(
"serde_json::Value::String('\\u{{{:x}}}'.to_string())",
u32::from(character)
))
}
TypeKind::Primitive(PrimitiveKind::Int64 | PrimitiveKind::UInt64) => Ok(format!(
"serde_json::Value::String(\"{value}\".to_string())"
)),
TypeKind::Primitive(kind) if is_integer(*kind) => {
Ok(format!("serde_json::Value::from({value}i64)"))
}
TypeKind::Enumeration { .. } => {
let mut name = None;
for id in ast.enumerators_of(type_) {
if ast.effective_enumerator_value(*id) == Some(value) {
if let Ok(node) = ast.enumerator(*id) {
name = Some(node.name.clone());
}
break;
}
}
let name = name.ok_or_else(|| ProbeError::NoLayout {
detail: format!(
"{path}: a case label names no enumerator of the discriminator"
),
})?;
Ok(format!("serde_json::Value::String(\"{name}\".to_string())"))
}
TypeKind::Alias { aliased } => {
let target = aliased.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{path}: the discriminator's typedef is unresolved"),
})?;
Self::discriminator(ast, target, value, path)
}
_ => Err(unfilled(
path,
"a union discriminator whose canonical form this emitter does not build",
)),
}
}
fn array(
&mut self,
ast: &Ast,
element: Id<TypeNode>,
expr: &str,
path: &str,
counts: &[usize],
) -> Result<String, ProbeError> {
let Some((count, rest)) = counts.split_first() else {
return self.value(ast, element, expr, path);
};
let mut items = Vec::with_capacity(*count);
for index in 0..*count {
items.push(self.array(
ast,
element,
&format!("{expr}[{index}]"),
&format!("{path}[{index}]"),
rest,
)?);
}
Ok(format!(
"serde_json::Value::Array(vec![{}])",
items.join(", ")
))
}
}
struct Accessor {
name: String,
type_: Id<TypeNode>,
accessor: String,
optional: bool,
}
fn rust_members(ast: &Ast, type_: Id<TypeNode>, expr: &str) -> Result<Vec<Accessor>, ProbeError> {
let mut out = Vec::new();
if let Ok(node) = ast.type_(type_) {
if let TypeKind::Structure {
base: Some(base), ..
} = &node.kind
{
let base = base.resolved().ok_or_else(|| ProbeError::NoLayout {
detail: format!("{expr}: the base type is unresolved"),
})?;
out.extend(rust_members(ast, base, &format!("{expr}.base"))?);
}
}
for id in ast.members_of(type_) {
let member = ast.member(*id).map_err(|_| ProbeError::NoLayout {
detail: format!("{expr}: a member does not resolve"),
})?;
let Some(member_type) = member.type_.resolved() else {
return Err(ProbeError::NoLayout {
detail: format!("{expr}.{}: the member type is unresolved", member.name),
});
};
out.push(Accessor {
name: member.name.clone(),
type_: member_type,
accessor: format!("{expr}.{}", rust_identifier(&member.name)),
optional: member.facets.is_optional(),
});
}
Ok(out)
}
fn primitive(kind: PrimitiveKind, expr: &str, path: &str) -> Result<String, ProbeError> {
Ok(match kind {
PrimitiveKind::Boolean => format!("serde_json::Value::Bool({expr})"),
PrimitiveKind::Char | PrimitiveKind::WChar => {
format!("serde_json::Value::String(({expr}).to_string())")
}
PrimitiveKind::Float32 => format!("zd_json_f32({expr})"),
PrimitiveKind::Float64 => format!("zd_json_f64({expr})"),
PrimitiveKind::Int64 | PrimitiveKind::UInt64 => {
format!("serde_json::Value::String(({expr}).to_string())")
}
PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32 => format!("serde_json::Value::from({expr})"),
PrimitiveKind::Float128 => {
return Err(unsupported(
path,
"a long double, which has no Rust primitive in hddsgen's output",
));
}
})
}
const fn is_integer(kind: PrimitiveKind) -> bool {
matches!(
kind,
PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
)
}
fn bitmask_constant(ast: &Ast, type_: Id<TypeNode>, flag: &str) -> String {
let scoped = rust_path(ast, type_);
let (prefix, type_name) = match scoped.rsplit_once("::") {
Some((module, name)) => (format!("{module}::"), name.to_string()),
None => (String::new(), scoped),
};
format!(
"{prefix}{}_{}",
type_name.to_uppercase(),
flag.to_uppercase()
)
}
fn unsupported(path: &str, kind: &'static str) -> ProbeError {
ProbeError::Unsupported {
path: path.to_string(),
kind,
}
}
fn unfilled(path: &str, kind: &'static str) -> ProbeError {
ProbeError::Unfilled {
path: path.to_string(),
kind,
}
}