use crate::adapter::cpp::{cpp_type_tag, cpp_value_type, cxx_name};
use crate::default_common::{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::common::{Doc, Ident, Literal, Path};
use zerodds_lang_cst::cpp;
#[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_{}", cpp_type_tag(ast, id)),
}
}
#[must_use]
pub fn default_into(ast: &Ast, id: Id<TypeNode>, place: cpp::Expr) -> cpp::Statement {
cpp::Statement::Expr(cpp::Expr::Call {
callee: Box::new(path(&defaulter_name(ast, id))),
args: vec![place],
})
}
fn external_slot(place: cpp::Expr) -> cpp::Expr {
cpp::Expr::Call {
callee: Box::new(cpp::Expr::Path(Path::new(&[
"zerodds",
"wire",
"external_slot",
]))),
args: vec![place],
}
}
pub fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: cpp::Expr,
) -> Result<Vec<cpp::Statement>, EncodeError> {
let place = if crate::adapter::held_externally(ast, node) {
external_slot(place)
} else {
place
};
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 cpp_defaulters_called_by(
ast: &Ast,
decls: &[cpp::Decl],
) -> Result<Vec<cpp::Decl>, EncodeError> {
cpp_defaulters_called_by_excluding(ast, decls, &[])
}
pub fn cpp_defaulters_called_by_excluding(
ast: &Ast,
decls: &[cpp::Decl],
excluded: &[Id<TypeNode>],
) -> Result<Vec<cpp::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)
.filter(|id| !excluded.contains(id))
{
wanted.push(id);
grew = true;
}
}
let produced = cpp_defaulters_over(ast, &wanted)?;
if !grew {
return Ok(produced);
}
for decl in &produced {
calls_in_decl(decl, &mut called);
}
}
cpp_defaulters_over(ast, &wanted)
}
#[must_use]
pub fn reachable_from(decls: Vec<cpp::Decl>, entry: &str) -> Vec<cpp::Decl> {
let mut wanted = vec![entry.to_string()];
let mut index = 0;
while index < wanted.len() {
let name = wanted[index].clone();
index += 1;
for decl in &decls {
if let cpp::Decl::Function(function) = decl
&& function.name.as_str() == name
{
let mut called = Vec::new();
calls_in_decl(decl, &mut called);
for callee in called {
if !wanted.contains(&callee) {
wanted.push(callee);
}
}
}
}
}
decls
.into_iter()
.filter(|decl| match decl {
cpp::Decl::Function(function) => {
wanted.iter().any(|kept| kept == function.name.as_str())
}
_ => true,
})
.collect()
}
fn defaulter_index(ast: &Ast) -> Vec<(String, Id<TypeNode>)> {
ast.types()
.map(|(id, _)| (defaulter_name(ast, id), id))
.collect()
}
fn calls_in_decl(decl: &cpp::Decl, out: &mut Vec<String>) {
match decl {
cpp::Decl::Function(function) => {
if let cpp::FunctionBody::Block(body) = &function.body {
calls_in_block(body, out);
}
}
cpp::Decl::Variable(variable) => {
if let Some(value) = &variable.value {
calls_in_expr(value, out);
}
}
cpp::Decl::Namespace(namespace) => {
for inner in &namespace.decls {
calls_in_decl(inner, out);
}
}
cpp::Decl::Template(template) => calls_in_decl(&template.decl, out),
cpp::Decl::Class(class) => {
for section in &class.sections {
for member in §ion.members {
calls_in_class_member(member, out);
}
}
}
cpp::Decl::Enum(_)
| cpp::Decl::Alias(_)
| cpp::Decl::Comment(_)
| cpp::Decl::Verbatim(_)
| cpp::Decl::Forward { .. } => {}
}
}
fn calls_in_class_member(member: &cpp::ClassMember, out: &mut Vec<String>) {
match member {
cpp::ClassMember::Method(method) => {
if let Some(body) = &method.body {
calls_in_block(body, out);
}
}
cpp::ClassMember::Constructor(constructor) => {
for (_, value) in &constructor.initialisers {
calls_in_expr(value, out);
}
if let cpp::FunctionBody::Block(body) = &constructor.body {
calls_in_block(body, out);
}
}
cpp::ClassMember::Destructor(destructor) => {
if let cpp::FunctionBody::Block(body) = &destructor.body {
calls_in_block(body, out);
}
}
cpp::ClassMember::Nested(decl) => calls_in_decl(decl, out),
cpp::ClassMember::Field(field) => {
if let Some(value) = &field.default {
calls_in_expr(value, out);
}
}
cpp::ClassMember::Alias(_)
| cpp::ClassMember::Comment(_)
| cpp::ClassMember::Verbatim(_) => {}
}
}
fn calls_in_block(block: &cpp::Block, out: &mut Vec<String>) {
for statement in &block.statements {
calls_in_statement(statement, out);
}
}
fn calls_in_statement(statement: &cpp::Statement, out: &mut Vec<String>) {
match statement {
cpp::Statement::Declare { value, .. } => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
cpp::Statement::Expr(expr) | cpp::Statement::Throw(expr) => calls_in_expr(expr, out),
cpp::Statement::Return(value) => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
cpp::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);
}
}
cpp::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);
}
cpp::Statement::While { condition, body } => {
calls_in_expr(condition, out);
calls_in_block(body, out);
}
cpp::Statement::Try { body, handlers } => {
calls_in_block(body, out);
for handler in handlers {
calls_in_block(&handler.body, out);
}
}
cpp::Statement::ForEach { range, body, .. } => {
calls_in_expr(range, out);
calls_in_block(body, out);
}
cpp::Statement::Switch { value, cases } => {
calls_in_expr(value, out);
for case in cases {
for label in &case.labels {
calls_in_expr(label, out);
}
calls_in_block(&case.body, out);
}
}
cpp::Statement::Break | cpp::Statement::Comment(_) => {}
}
}
fn calls_in_expr(expr: &cpp::Expr, out: &mut Vec<String>) {
match expr {
cpp::Expr::Call { callee, args } => {
if let cpp::Expr::Path(path) = callee.as_ref()
&& let Some(last) = path.last()
{
let name = last.as_str();
if name.starts_with("zd_") && !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);
}
}
cpp::Expr::MethodCall { receiver, args, .. } => {
calls_in_expr(receiver, out);
for arg in args {
calls_in_expr(arg, out);
}
}
cpp::Expr::Member { value, .. }
| cpp::Expr::Arrow { value, .. }
| cpp::Expr::AddressOf(value)
| cpp::Expr::Deref(value)
| cpp::Expr::Paren(value)
| cpp::Expr::Cast { value, .. } => calls_in_expr(value, out),
cpp::Expr::Index { value, index } => {
calls_in_expr(value, out);
calls_in_expr(index, out);
}
cpp::Expr::Binary { lhs, rhs, .. } => {
calls_in_expr(lhs, out);
calls_in_expr(rhs, out);
}
cpp::Expr::Unary { value, .. } => calls_in_expr(value, out),
cpp::Expr::Construct { args, .. } | cpp::Expr::InitList(args) => {
for arg in args {
calls_in_expr(arg, out);
}
}
cpp::Expr::Literal(_) | cpp::Expr::Path(_) | cpp::Expr::SizeOf(_) => {}
}
}
pub fn cpp_defaulters_over(
ast: &Ast,
types: &[Id<TypeNode>],
) -> Result<Vec<cpp::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<cpp::Block>,
) -> Result<cpp::Decl, EncodeError> {
let type_ = cpp_value_type(ast, id, function_name).map_err(|error| EncodeError::NoWriter {
member: cpp_type_tag(ast, id),
kind: match error {
crate::adapter::cst::MaterialiseError::NoLanguageForm { .. } => {
"type with no C++ form, whose Table 9 default therefore has nowhere to go"
}
_ => "type whose C++ spelling does not evaluate",
},
})?;
Ok(cpp::Decl::Function(cpp::Function {
doc: Doc::none(),
returns: cpp::Type::Void,
name: Ident::new(function_name),
params: vec![cpp::Param {
type_: cpp::Type::Ref(Box::new(type_)),
name: Some(Ident::new("v")),
default: None,
}],
is_inline: false,
is_static: true,
is_constexpr: false,
is_noexcept: false,
is_maybe_unused: false,
body: body.map_or(cpp::FunctionBody::None, cpp::FunctionBody::Block),
}))
}
#[allow(clippy::too_many_lines)]
fn body_of(ast: &Ast, id: Id<TypeNode>, own_name: &str) -> Result<Option<cpp::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(name("v"), primitive_default(*kind))],
TypeKind::String { .. } => vec![clear("v")],
TypeKind::Fixed { scale, .. } => {
vec![assign(name("v"), string_literal(&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![cpp::Statement::Expr(cpp::Expr::Call {
callee: Box::new(path(&defaulter_name(ast, target))),
args: vec![name("v")],
})]
}
TypeKind::Enumeration { .. } => {
let Some(literal) = crate::emit::default_enumerator_name(ast, id) else {
return Err(EncodeError::NoWriter {
member: own_name.to_string(),
kind: "enumeration with no enumerator to take as its default",
});
};
vec![assign(
name("v"),
path(&crate::emit::cpp::enumerator_constant_name(
ast, id, &literal,
)),
)]
}
TypeKind::Bitmask { .. } => vec![assign(name("v"), cpp::Expr::Literal(Literal::int(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(
member("v", &cxx_name(field_name)),
cpp::Expr::Literal(Literal::int(0)),
));
}
}
statements
}
TypeKind::Sequence { .. } | TypeKind::Map { .. } => vec![clear("v")],
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 = cpp::Expr::Index {
value: Box::new(access),
index: Box::new(name(&format!("zd_i{level}"))),
};
}
let mut inner = cpp::Block {
statements: vec![default_into(ast, element, access)],
};
for (level, length) in lengths.iter().enumerate().rev() {
inner = cpp::Block {
statements: vec![counted_loop(&format!("zd_i{level}"), *length, inner)],
};
}
inner.statements
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let mut statements = Vec::new();
for member_id in &ast.all_members_of(id) {
let Ok(member_node) = ast.member(*member_id) else {
continue;
};
let field = cxx_name(&member_node.name);
if member_node.facets.is_optional() {
statements.push(cpp::Statement::Expr(cpp::Expr::Call {
callee: Box::new(cpp::Expr::Path(Path::new(&[
"zerodds", "wire", "destroy",
]))),
args: vec![member("v", &field)],
}));
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",
});
};
statements.extend(member_default(
ast,
member_node,
member_type,
member("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,
member("v", crate::adapter::cpp::UNION_DISCRIMINATOR),
)];
if let Some((member_type, field)) = selected_by_default(ast, id, discriminator)? {
statements.push(default_into(
ast,
member_type,
member("v", &crate::adapter::cpp::union_storage(&field)),
));
}
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",
});
}
};
let mut statements = statements;
if statements.is_empty() {
statements.push(cpp::Statement::Expr(cpp::Expr::Cast {
kind: cpp::CastKind::Static,
type_: Box::new(cpp::Type::Void),
value: Box::new(name("v")),
}));
}
Ok(Some(cpp::Block { statements }))
}
fn primitive_default(kind: PrimitiveKind) -> cpp::Expr {
match kind {
PrimitiveKind::Boolean => cpp::Expr::Literal(Literal::Bool(false)),
PrimitiveKind::Char | PrimitiveKind::WChar => cpp::Expr::Literal(Literal::int(0)),
PrimitiveKind::Float32 => cpp::Expr::Literal(Literal::Float("0.0f".to_string())),
PrimitiveKind::Float64 => cpp::Expr::Literal(Literal::Float("0.0".to_string())),
PrimitiveKind::Float128 => cpp::Expr::Call {
callee: Box::new(cpp::Expr::Path(Path::rooted(&[
"zerodds", "wire", "Float128", "zero",
]))),
args: Vec::new(),
},
PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
| PrimitiveKind::Int64
| PrimitiveKind::UInt64 => cpp::Expr::Literal(Literal::int(0)),
}
}
fn selected_by_default(
ast: &Ast,
id: Id<TypeNode>,
discriminator: Id<TypeNode>,
) -> Result<Option<(Id<TypeNode>, String)>, 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, cxx_name(&member.name))))
}
fn discriminator_default_value(ast: &Ast, discriminator: Id<TypeNode>) -> Option<i128> {
let discriminator = resolve_alias(ast, discriminator);
match ast.type_(discriminator).map(|node| node.kind.clone()) {
Ok(TypeKind::Enumeration { .. }) => {
let literal = crate::emit::default_enumerator_name(ast, discriminator)?;
for enumerator in ast.enumerators_of(discriminator) {
let Ok(node) = ast.enumerator(*enumerator) else {
return None;
};
if node.name == literal {
return ast.effective_enumerator_value(*enumerator);
}
}
None
}
Ok(TypeKind::Primitive(_)) => Some(0),
_ => None,
}
}
fn explicit(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
type_: Id<TypeNode>,
member_name: &str,
) -> Result<cpp::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(cpp::Expr::Literal(Literal::Bool(*flag)))
}
(ConstExpr::Literal(AstLiteral::Char(ch)), _) => {
Ok(cpp::Expr::Literal(Literal::int(i128::from(u32::from(*ch)))))
}
(ConstExpr::Literal(AstLiteral::WChar(code)), _) => {
Ok(cpp::Expr::Literal(Literal::int(i128::from(*code))))
}
(ConstExpr::Literal(AstLiteral::String(text)), _) => Ok(string_literal(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(string_literal(&text))
}
(ConstExpr::Literal(AstLiteral::Float { mantissa, exponent }), kind) => {
if matches!(kind, Ok(TypeKind::Primitive(PrimitiveKind::Float128))) {
return Err(refuse(
"a `@default` on a `long double`, which has no C++ literal",
));
}
let suffix = match kind {
Ok(TypeKind::Primitive(PrimitiveKind::Float32)) => "f",
_ => "",
};
let exponent = exponent
.as_ref()
.map_or(String::new(), |text| format!("e{text}"));
Ok(cpp::Expr::Literal(Literal::Float(format!(
"{mantissa}{exponent}{suffix}"
))))
}
(ConstExpr::Literal(AstLiteral::Fixed { digits, scale }), _) => {
Ok(string_literal(&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(path(&crate::emit::cpp::enumerator_constant_name(
ast,
resolved,
&enumerator.name,
)))
}
(_, kind) => {
let value = ast
.eval_integral(expr)
.map_err(|_| refuse("a `@default` this frontend cannot evaluate"))?;
Ok(cpp::Expr::Literal(if value > i128::from(i64::MAX) {
Literal::Int(format!("{value}u"))
} else {
let _ = kind;
Literal::Int(zerodds_lang_render::cpp::integer_text(value))
}))
}
}
}
fn name(text: &str) -> cpp::Expr {
cpp::Expr::Path(Path::single(text))
}
fn path(text: &str) -> cpp::Expr {
let rooted = text.starts_with("::");
let segments: Vec<&str> = text.trim_start_matches("::").split("::").collect();
cpp::Expr::Path(if rooted {
Path::rooted(&segments)
} else {
Path::new(&segments)
})
}
fn string_literal(text: &str) -> cpp::Expr {
cpp::Expr::Literal(Literal::Str(text.to_string()))
}
fn member(base: &str, field: &str) -> cpp::Expr {
cpp::Expr::Member {
value: Box::new(name(base)),
name: Ident::new(field),
}
}
fn clear(base: &str) -> cpp::Statement {
cpp::Statement::Expr(cpp::Expr::MethodCall {
receiver: Box::new(name(base)),
name: Ident::new("clear"),
args: Vec::new(),
})
}
fn assign(place: cpp::Expr, value: cpp::Expr) -> cpp::Statement {
cpp::Statement::Expr(cpp::Expr::Binary {
op: "=",
lhs: Box::new(place),
rhs: Box::new(value),
})
}
fn counted_loop(index: &str, count: i128, body: cpp::Block) -> cpp::Statement {
cpp::Statement::For {
init: Box::new(cpp::Statement::Declare {
type_: cpp::Type::named(Path::new(&["std", "size_t"])),
name: Ident::new(index),
value: Some(cpp::Expr::Literal(Literal::int(0))),
}),
condition: cpp::Expr::Binary {
op: "<",
lhs: Box::new(name(index)),
rhs: Box::new(cpp::Expr::Literal(Literal::Int(format!("{count}u")))),
},
step: cpp::Expr::Binary {
op: "=",
lhs: Box::new(name(index)),
rhs: Box::new(cpp::Expr::Binary {
op: "+",
lhs: Box::new(name(index)),
rhs: Box::new(cpp::Expr::Literal(Literal::int(1))),
}),
},
body,
}
}