use crate::adapter::c::{c_type_name, c_value_type, module_prefix};
use crate::default_common::{discriminator_default_value, resolve_alias, selected_case};
use crate::encode::EncodeError;
use crate::fixed_literal::{fixed_text, zero_decimal};
use zerodds_ast::{
Ast, ConstExpr, Literal as AstLiteral, MemberNode, PrimitiveKind, TypeKind, TypeNode,
};
use zerodds_ast_core::Id;
use zerodds_lang_cst::c;
use zerodds_lang_cst::common::{Doc, Ident, Literal};
#[must_use]
pub fn defaulter_name(ast: &Ast, id: Id<TypeNode>) -> String {
match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => {
format!("zd_def_p_{}", kind.as_str().replace(' ', "_"))
}
_ => format!("zd_def_{}", c_type_name(ast, id)),
}
}
pub fn default_into(ast: &Ast, id: Id<TypeNode>, place: c::Expr) -> c::Statement {
let mut args = Vec::new();
if takes_reader(ast, id) {
args.push(name("r"));
}
args.push(destination(ast, id, place));
c::Statement::Expr(c::Expr::Call {
callee: Box::new(name(&defaulter_name(ast, id))),
args,
})
}
#[must_use]
pub fn takes_reader(ast: &Ast, id: Id<TypeNode>) -> bool {
let mut seen = Vec::new();
reaches_held_storage(ast, id, &mut seen)
}
fn reaches_held_storage(ast: &Ast, id: Id<TypeNode>, seen: &mut Vec<Id<TypeNode>>) -> bool {
if seen.contains(&id) {
return false;
}
seen.push(id);
let Ok(node) = ast.type_(id) else {
return false;
};
match &node.kind {
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
for crate::adapter::c::CMember {
handle: member,
owner,
..
} in &crate::adapter::c::c_members(ast, id)
{
let Ok(member_node) = ast.member(*member) else {
continue;
};
if member_node.facets.is_optional() {
continue;
}
let Some(member_type) = member_node.type_.resolved() else {
continue;
};
if crate::adapter::c::member_held_behind_pointer(
ast,
*owner,
member_node,
member_type,
) || reaches_held_storage(ast, member_type, seen)
{
return true;
}
}
false
}
TypeKind::Union { discriminator, .. } => {
let Some(discriminator) = discriminator.resolved() else {
return false;
};
match selected_by_default(ast, id, discriminator) {
Ok(Some((member_type, _, external))) => {
crate::adapter::c::pointer_member(ast, id, member_type, external)
|| reaches_held_storage(ast, member_type, seen)
}
_ => false,
}
}
TypeKind::Alias { aliased } => aliased
.resolved()
.is_some_and(|target| reaches_held_storage(ast, target, seen)),
TypeKind::Array { element, .. } => element
.resolved()
.is_some_and(|element| reaches_held_storage(ast, element, seen)),
_ => false,
}
}
fn held_default(
ast: &Ast,
member_type: Id<TypeNode>,
place: c::Expr,
write: impl FnOnce(c::Expr) -> Result<Vec<c::Statement>, EncodeError>,
) -> Result<Vec<c::Statement>, EncodeError> {
use crate::emit::CodecTarget;
let (mut statements, through) = crate::emit::c::C::make_indirection(ast, member_type, place);
statements.extend(write(through)?);
Ok(statements)
}
pub fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: c::Expr,
) -> Result<Vec<c::Statement>, EncodeError> {
let Some(expr) = node.facets.xtypes.as_ref().and_then(|x| x.default_value) else {
return Ok(vec![default_into(ast, type_, place)]);
};
let value = explicit(ast, expr, type_, &node.name)?;
Ok(vec![assign(place, value)])
}
pub fn c_defaulters_called_by(ast: &Ast, decls: &[c::Decl]) -> Result<Vec<c::Decl>, EncodeError> {
let index = defaulter_index(ast);
let mut called: Vec<String> = Vec::new();
for decl in decls {
calls_in_decl(decl, &mut called);
}
let mut wanted: Vec<Id<TypeNode>> = Vec::new();
let mut have: Vec<String> = Vec::new();
for _ in 0..=index.len() {
let mut grew = false;
for name in &called {
if have.contains(name) {
continue;
}
have.push(name.clone());
if let Some(id) = index.iter().find(|(n, _)| n == name).map(|(_, id)| *id) {
wanted.push(id);
grew = true;
}
}
let produced = c_defaulters_over(ast, &wanted)?;
if !grew {
return Ok(produced);
}
for decl in &produced {
calls_in_decl(decl, &mut called);
}
}
c_defaulters_over(ast, &wanted)
}
fn defaulter_index(ast: &Ast) -> Vec<(String, Id<TypeNode>)> {
ast.types()
.map(|(id, _)| (defaulter_name(ast, id), id))
.collect()
}
fn calls_in_decl(decl: &c::Decl, out: &mut Vec<String>) {
match decl {
c::Decl::Function(function) => {
if let Some(body) = &function.body {
calls_in_block(body, out);
}
}
c::Decl::Variable(variable) => {
if let Some(value) = &variable.value {
calls_in_expr(value, out);
}
}
c::Decl::Define(define) => {
if let Some(value) = &define.value {
calls_in_expr(value, out);
}
}
c::Decl::Typedef(_)
| c::Decl::Struct(_)
| c::Decl::Union(_)
| c::Decl::Enum(_)
| c::Decl::Comment(_)
| c::Decl::Verbatim(_) => {}
}
}
fn calls_in_block(block: &c::Block, out: &mut Vec<String>) {
for statement in &block.statements {
calls_in_statement(statement, out);
}
}
fn calls_in_statement(statement: &c::Statement, out: &mut Vec<String>) {
match statement {
c::Statement::Declare { value, .. } => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
c::Statement::Expr(expr) => calls_in_expr(expr, out),
c::Statement::Return(value) => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
c::Statement::If {
condition,
then,
otherwise,
} => {
calls_in_expr(condition, out);
calls_in_block(then, out);
if let Some(otherwise) = otherwise {
calls_in_block(otherwise, out);
}
}
c::Statement::For {
init,
condition,
step,
body,
} => {
calls_in_statement(init, out);
calls_in_expr(condition, out);
calls_in_expr(step, out);
calls_in_block(body, out);
}
c::Statement::While { condition, body } => {
calls_in_expr(condition, out);
calls_in_block(body, out);
}
c::Statement::Break | c::Statement::Comment(_) => {}
}
}
fn calls_in_expr(expr: &c::Expr, out: &mut Vec<String>) {
match expr {
c::Expr::Call { callee, args } => {
if let c::Expr::Name(name) = callee.as_ref() {
let name = name.as_str();
if name.starts_with("zd_def_") && !out.iter().any(|seen| seen == name) {
out.push(name.to_string());
}
}
calls_in_expr(callee, out);
for arg in args {
calls_in_expr(arg, out);
}
}
c::Expr::Member { value, .. }
| c::Expr::Arrow { value, .. }
| c::Expr::Unary { value, .. }
| c::Expr::AddressOf(value)
| c::Expr::Deref(value)
| c::Expr::Paren(value)
| c::Expr::Cast { value, .. } => calls_in_expr(value, out),
c::Expr::Index { value, index } => {
calls_in_expr(value, out);
calls_in_expr(index, out);
}
c::Expr::Binary { lhs, rhs, .. } => {
calls_in_expr(lhs, out);
calls_in_expr(rhs, out);
}
c::Expr::Initializer(items) => {
for item in items {
calls_in_expr(item, out);
}
}
c::Expr::Literal(_) | c::Expr::Name(_) | c::Expr::SizeOf(_) => {}
}
}
pub fn c_defaulters_over(ast: &Ast, types: &[Id<TypeNode>]) -> Result<Vec<c::Decl>, EncodeError> {
let mut prototypes = Vec::new();
let mut bodies = Vec::new();
let mut emitted: Vec<String> = Vec::new();
for id in types.iter().copied() {
let function_name = defaulter_name(ast, id);
if emitted.contains(&function_name) {
continue;
}
let Some(body) = body_of(ast, id, &function_name)? else {
continue;
};
emitted.push(function_name.clone());
prototypes.push(function(ast, id, &function_name, None));
bodies.push(function(ast, id, &function_name, Some(body)));
}
prototypes.extend(bodies);
Ok(prototypes)
}
fn function(ast: &Ast, id: Id<TypeNode>, function_name: &str, body: Option<c::Block>) -> c::Decl {
let value = if is_array(ast, id, 0) {
c::Param {
type_: c_value_type(ast, id),
name: Some(Ident::new("v")),
}
} else {
c::Param {
type_: c::Type::Pointer(Box::new(c_value_type(ast, id))),
name: Some(Ident::new("v")),
}
};
c::Decl::Function(c::Function {
doc: Doc::none(),
is_static: true,
is_inline: false,
is_maybe_unused: false,
returns: c::Type::Void,
name: Ident::new(function_name),
params: if takes_reader(ast, id) {
vec![
c::Param {
type_: c::Type::Pointer(Box::new(c::Type::Named(Ident::new("zdw_reader")))),
name: Some(Ident::new("r")),
},
value,
]
} else {
vec![value]
},
body,
})
}
#[allow(clippy::too_many_lines)]
fn body_of(ast: &Ast, id: Id<TypeNode>, own_name: &str) -> Result<Option<c::Block>, EncodeError> {
let Ok(node) = ast.type_(id) else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "a type the model does not hold",
});
};
let statements = match &node.kind {
TypeKind::Primitive(kind) => vec![assign(deref("v"), primitive_default(*kind))],
TypeKind::String { .. } => vec![assign(deref("v"), literal("\"\""))],
TypeKind::Fixed { scale, .. } => vec![assign(
deref("v"),
literal(&format!("\"{}\"", zero_decimal(*scale))),
)],
TypeKind::Alias { aliased } => {
let Some(target) = aliased.resolved() else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "typedef with an unresolved target",
});
};
vec![c::Statement::Expr(c::Expr::Call {
callee: Box::new(name(&defaulter_name(ast, target))),
args: vec![name("v")],
})]
}
TypeKind::Enumeration { .. } => {
let Some(constant) = default_enumerator(ast, id) else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "enumeration with no enumerator to take as its default",
});
};
vec![assign(deref("v"), name(&constant))]
}
TypeKind::Bitmask { .. } => vec![assign(deref("v"), literal("0"))],
TypeKind::Bitset { .. } => {
let mut statements = Vec::new();
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
if let Some(field_name) = field_node.name.as_ref() {
statements.push(assign(
arrow("v", &c::escape_keyword(field_name)),
literal("0"),
));
}
}
statements
}
TypeKind::Sequence { .. } | TypeKind::Map { .. } => vec![
assign(arrow("v", "_maximum"), literal("0u")),
assign(arrow("v", "_length"), literal("0u")),
assign(arrow("v", "_buffer"), name("NULL")),
assign(arrow("v", "_release"), literal("false")),
],
TypeKind::Array { element, dims } => {
let Some(element) = element.resolved() else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "array with an unresolved element",
});
};
let mut lengths = Vec::new();
for dim in dims {
lengths.push(ast.eval_integral(*dim).map_err(|_| EncodeError::NoWriter {
member: own_name.to_string(),
kind: "array whose length does not evaluate",
})?);
}
let mut access = name("v");
for level in 0..lengths.len() {
access = c::Expr::Index {
value: Box::new(access),
index: Box::new(name(&format!("zd_i{level}"))),
};
}
let mut inner = c::Block {
statements: vec![default_into(ast, element, access)],
};
for (level, length) in lengths.iter().enumerate().rev() {
inner = c::Block {
statements: vec![counted_loop(
&format!("zd_i{level}"),
literal(&format!("{length}u")),
inner,
)],
};
}
inner.statements
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let mut statements = Vec::new();
for crate::adapter::c::CMember {
handle: member,
path,
owner,
} in &crate::adapter::c::c_members(ast, id)
{
let Ok(member_node) = ast.member(*member) else {
continue;
};
let field = format!("{path}{}", c::escape_keyword(&member_node.name));
if member_node.facets.is_optional() {
statements.push(assign(arrow("v", &field), name("NULL")));
continue;
}
let Some(member_type) = member_node.type_.resolved() else {
return Err(EncodeError::NoWriter {
member: member_node.name.clone(),
kind: "member with an unresolved type",
});
};
if crate::adapter::c::member_held_behind_pointer(
ast,
*owner,
member_node,
member_type,
) {
statements.extend(held_default(
ast,
member_type,
arrow("v", &field),
|through| member_default(ast, member_node, member_type, through),
)?);
continue;
}
statements.extend(member_default(
ast,
member_node,
member_type,
arrow("v", &field),
)?);
}
statements
}
TypeKind::Union { discriminator, .. } => {
let Some(discriminator) = discriminator.resolved() else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "union with an unresolved discriminator",
});
};
let mut statements = vec![default_into(ast, discriminator, arrow("v", "_d"))];
if let Some((member_type, field, external)) =
selected_by_default(ast, id, discriminator)?
{
let place = c::Expr::Member {
value: Box::new(arrow("v", "_u")),
name: Ident::new(&field),
};
if crate::adapter::c::pointer_member(ast, id, member_type, external) {
statements.extend(held_default(ast, member_type, place, |through| {
Ok(vec![default_into(ast, member_type, through)])
})?);
} else {
statements.push(default_into(ast, member_type, place));
}
}
statements
}
TypeKind::Native | TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
return Ok(None);
}
TypeKind::ValueBox { .. } => {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "boxed value, whose presence flag has no settled wire form",
});
}
TypeKind::Any => {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "`any`, which DDS-XTypes has no encoding for; only CORBA CDR writes one, \
as a TypeCode ahead of the value",
});
}
TypeKind::ValueBase => {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "`ValueBase`, which DDS-XTypes has no encoding for; only CORBA CDR writes \
one, as a chunked value with its repository id",
});
}
};
Ok(Some(c::Block { statements }))
}
fn primitive_default(kind: PrimitiveKind) -> c::Expr {
match kind {
PrimitiveKind::Boolean => literal("false"),
PrimitiveKind::Char | PrimitiveKind::WChar => literal("0"),
PrimitiveKind::Float32 => literal("0.0f"),
PrimitiveKind::Float64 => literal("0.0"),
PrimitiveKind::Float128 => c::Expr::Call {
callee: Box::new(name("zdw_f128_zero")),
args: Vec::new(),
},
PrimitiveKind::Int64 => literal("INT64_C(0)"),
PrimitiveKind::UInt64 => literal("UINT64_C(0)"),
PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32 => literal("0"),
}
}
pub(crate) fn default_enumerator(ast: &Ast, id: Id<TypeNode>) -> Option<String> {
let name = crate::emit::default_enumerator_name(ast, id)?;
Some(format!(
"{}{}",
module_prefix(ast, id),
c::escape_keyword(&name)
))
}
fn selected_by_default(
ast: &Ast,
id: Id<TypeNode>,
discriminator: Id<TypeNode>,
) -> Result<Option<(Id<TypeNode>, String, bool)>, EncodeError> {
let Some(selector) = discriminator_default_value(ast, discriminator) else {
return Ok(None);
};
Ok(
selected_case(ast, id, selector)?.map(|(member, member_type)| {
(
member_type,
c::escape_keyword(&member.name),
crate::adapter::annotated_external(member),
)
}),
)
}
fn explicit(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
type_: Id<TypeNode>,
member_name: &str,
) -> Result<c::Expr, EncodeError> {
let refuse = |kind: &'static str| EncodeError::NoWriter {
member: member_name.to_string(),
kind,
};
let node = ast
.expr(expr)
.map_err(|_| refuse("a `@default` this frontend cannot follow"))?;
let resolved = resolve_alias(ast, type_);
match (
&node.expr,
ast.type_(resolved).map(|node| node.kind.clone()),
) {
(ConstExpr::Literal(AstLiteral::Boolean(flag)), _) => {
Ok(literal(if *flag { "true" } else { "false" }))
}
(ConstExpr::Literal(AstLiteral::Char(ch)), _) => {
Ok(literal(&format!("{}", u32::from(*ch))))
}
(ConstExpr::Literal(AstLiteral::WChar(code)), _) => Ok(literal(&format!("{code}"))),
(ConstExpr::Literal(AstLiteral::String(text)), _) => {
Ok(literal(&format!("\"{}\"", c_escape(text))))
}
(ConstExpr::Literal(AstLiteral::WString(codes)), _) => {
let mut text = String::new();
for code in codes {
let Some(ch) = char::from_u32(*code) else {
return Err(refuse("a `@default` wide string that is not code points"));
};
text.push(ch);
}
Ok(literal(&format!("\"{}\"", c_escape(&text))))
}
(ConstExpr::Literal(AstLiteral::Float { mantissa, exponent }), kind) => {
let suffix = match kind {
Ok(TypeKind::Primitive(PrimitiveKind::Float32)) => "f",
_ => "",
};
let exponent = exponent
.as_ref()
.map_or(String::new(), |text| format!("e{text}"));
Ok(literal(&format!("{mantissa}{exponent}{suffix}")))
}
(ConstExpr::Literal(AstLiteral::Fixed { digits, scale }), _) => {
Ok(literal(&format!("\"{}\"", fixed_text(digits, *scale))))
}
(ConstExpr::Literal(AstLiteral::Enumerator(reference)), _) => {
let target = reference
.resolved()
.ok_or_else(|| refuse("a `@default` enumerator that is unresolved"))?;
let enumerator = ast
.enumerator(target)
.map_err(|_| refuse("a `@default` enumerator this frontend cannot follow"))?;
Ok(name(&format!(
"{}{}",
module_prefix(ast, resolved),
c::escape_keyword(&enumerator.name)
)))
}
(_, kind) => {
let value = ast
.eval_integral(expr)
.map_err(|_| refuse("a `@default` this frontend cannot evaluate"))?;
Ok(match kind {
Ok(TypeKind::Primitive(PrimitiveKind::Int64)) if value == i128::from(i64::MIN) => {
literal(&zerodds_lang_render::c::integer_text(value))
}
Ok(TypeKind::Primitive(PrimitiveKind::Int64)) => {
literal(&format!("INT64_C({value})"))
}
Ok(TypeKind::Primitive(PrimitiveKind::UInt64)) => {
literal(&format!("UINT64_C({value})"))
}
_ => literal(&format!("{value}")),
})
}
}
}
fn c_escape(text: &str) -> String {
let mut out = String::with_capacity(text.len());
for ch in text.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
_ => out.push(ch),
}
}
out
}
fn is_array(ast: &Ast, id: Id<TypeNode>, depth: usize) -> bool {
if depth > 64 {
return false;
}
match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Array { .. }) => true,
Ok(TypeKind::Alias { aliased }) => aliased
.resolved()
.is_some_and(|target| is_array(ast, target, depth + 1)),
_ => false,
}
}
fn destination(ast: &Ast, id: Id<TypeNode>, place: c::Expr) -> c::Expr {
if is_array(ast, id, 0) {
return place;
}
match place {
c::Expr::Deref(pointer) => *pointer,
place => c::Expr::AddressOf(Box::new(place)),
}
}
fn name(text: &str) -> c::Expr {
c::Expr::Name(Ident::new(text))
}
fn literal(text: &str) -> c::Expr {
c::Expr::Literal(Literal::Int(text.to_string()))
}
fn deref(text: &str) -> c::Expr {
c::Expr::Deref(Box::new(name(text)))
}
fn arrow(value: &str, field: &str) -> c::Expr {
c::Expr::Arrow {
value: Box::new(name(value)),
name: Ident::new(field),
}
}
fn assign(place: c::Expr, value: c::Expr) -> c::Statement {
c::Statement::Expr(c::Expr::Binary {
op: "=",
lhs: Box::new(place),
rhs: Box::new(value),
})
}
fn counted_loop(index: &str, count: c::Expr, body: c::Block) -> c::Statement {
c::Statement::For {
init: Box::new(c::Statement::Declare {
type_: c::Type::Primitive(c::Primitive::UInt32),
name: Ident::new(index),
value: Some(literal("0u")),
}),
condition: c::Expr::Binary {
op: "<",
lhs: Box::new(name(index)),
rhs: Box::new(count),
},
step: c::Expr::Binary {
op: "=",
lhs: Box::new(name(index)),
rhs: Box::new(c::Expr::Binary {
op: "+",
lhs: Box::new(name(index)),
rhs: Box::new(literal("1u")),
}),
},
body,
}
}