use crate::default_common::{discriminator_default_value, resolve_alias, selected_case};
use crate::emit::CodecTarget;
use crate::emit::node::Node;
use crate::encode::EncodeError;
use crate::fixed_literal::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, Literal};
use zerodds_lang_cst::node::{Expr, Function, Stat};
const MAX_ALIAS_DEPTH: usize = 64;
#[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_{}", Node::type_name(ast, id)),
}
}
#[must_use]
pub fn defaulter_from_decoder(decoder: &str) -> String {
match decoder.strip_prefix("zd_dec_") {
Some(rest) => format!("zd_def_{rest}"),
None => format!("zd_def_{decoder}"),
}
}
#[must_use]
pub fn default_into(ast: &Ast, id: Id<TypeNode>, place: Expr) -> Stat {
Stat::Assign {
doc: Doc::none(),
place,
value: Expr::call(&defaulter_name(ast, id), Vec::new()),
}
}
pub fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: Expr,
) -> Result<Vec<Stat>, EncodeError> {
Ok(vec![Stat::Assign {
doc: Doc::none(),
place,
value: member_value(ast, node, type_)?,
}])
}
pub fn node_defaulters_called_by(ast: &Ast, decls: &[Stat]) -> Result<Vec<Stat>, EncodeError> {
let index: Vec<(String, Id<TypeNode>)> = ast
.types()
.map(|(id, _)| (defaulter_name(ast, id), id))
.collect();
let mut called: Vec<String> = Vec::new();
for decl in decls {
calls_in_stat(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 = node_defaulters_over(ast, &wanted)?;
if !grew {
return Ok(produced);
}
for decl in &produced {
calls_in_stat(decl, &mut called);
}
}
node_defaulters_over(ast, &wanted)
}
pub fn node_defaulters_over(ast: &Ast, types: &[Id<TypeNode>]) -> Result<Vec<Stat>, EncodeError> {
let mut out = Vec::new();
let mut emitted: Vec<String> = Vec::new();
for id in types.iter().copied() {
let name = defaulter_name(ast, id);
if emitted.contains(&name) {
continue;
}
let Some(value) = value_of(ast, id, &name)? else {
continue;
};
emitted.push(name.clone());
out.push(Stat::Function(Function {
doc: Doc::line(
"Table 9 of DDS-XTypes 1.3: what this type is where the stream did not carry it.",
),
name: zerodds_lang_cst::common::Ident::new(&name),
params: Vec::new(),
body: vec![Stat::Return(Some(value))],
}));
}
Ok(out)
}
#[allow(clippy::too_many_lines)]
fn value_of(ast: &Ast, id: Id<TypeNode>, own_name: &str) -> Result<Option<Expr>, EncodeError> {
let id = resolve_alias(ast, id);
let refuse = |kind: &'static str| EncodeError::NoWriter {
member: own_name.to_string(),
kind,
};
let Ok(type_node) = ast.type_(id) else {
return Err(refuse("a type the model does not hold"));
};
let value = match &type_node.kind {
TypeKind::Primitive(kind) => primitive_default(*kind),
TypeKind::String { .. } => Expr::string(""),
TypeKind::Fixed { scale, .. } => Expr::string(&zero_decimal(*scale)),
TypeKind::Alias { .. } => {
return Err(refuse("typedef with an unresolved target"));
}
TypeKind::Enumeration { .. } => {
let Some(enumerator) = crate::emit::default_enumerator_name(ast, id) else {
return Err(refuse(
"enumeration with no enumerator to take as its default",
));
};
Node::enumerator_constant(ast, id, &enumerator)
}
TypeKind::Bitmask { .. } => {
if ast.effective_bit_bound(id) > 32 {
Node::wide_unsigned(0)
} else {
Expr::int(0)
}
}
TypeKind::Bitset { .. } => {
let mut entries = Vec::new();
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
return Err(refuse("a bitfield the model does not hold"));
};
let Some(field_name) = field_node.name.as_ref() else {
continue;
};
entries.push((
zerodds_lang_cst::common::Ident::new(field_name),
Expr::int(0),
));
}
Expr::Object(entries)
}
TypeKind::Sequence { .. } | TypeKind::Map { .. } => Expr::Array(Vec::new()),
TypeKind::Array { element, dims } => {
let Some(element) = element.resolved() else {
return Err(refuse("array with an unresolved element"));
};
let mut lengths = Vec::new();
for dim in dims {
let length = ast
.eval_integral(*dim)
.map_err(|_| refuse("array whose length does not evaluate"))?;
lengths.push(
usize::try_from(length)
.map_err(|_| refuse("array of a length this column cannot spell"))?,
);
}
let mut out = Expr::call(&defaulter_name(ast, element), Vec::new());
for length in lengths.into_iter().rev() {
out = Expr::Array(vec![out; length]);
}
out
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let mut entries = Vec::new();
if let Some(base) = crate::adapter::c::struct_base(ast, id) {
entries.push((
zerodds_lang_cst::common::Ident::new("parent"),
Expr::call(&defaulter_name(ast, base), Vec::new()),
));
}
for member_id in ast.members_of(id) {
let Ok(member_node) = ast.member(*member_id) else {
return Err(refuse("a member the model does not hold"));
};
if member_node.facets.is_optional() {
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",
});
};
entries.push((
zerodds_lang_cst::common::Ident::new(&member_node.name),
member_value(ast, member_node, member_type)?,
));
}
Expr::Object(entries)
}
TypeKind::Union { discriminator, .. } => {
let Some(discriminator) = discriminator.resolved() else {
return Err(refuse("union with an unresolved discriminator"));
};
let selector = discriminator_default_value(ast, discriminator)
.ok_or_else(|| refuse("union whose discriminator type has no default value"))?;
let mut entries = vec![(
zerodds_lang_cst::common::Ident::new("_d"),
discriminant_literal(ast, discriminator, selector),
)];
if let Some((member_type, field)) = selected_by_default(ast, id, selector)? {
entries.push((
zerodds_lang_cst::common::Ident::new(&field),
Expr::call(&defaulter_name(ast, member_type), Vec::new()),
));
}
Expr::Object(entries)
}
TypeKind::Native | TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
return Ok(None);
}
TypeKind::ValueBox { .. } => {
return Err(refuse(
"boxed value, whose presence flag has no settled wire form",
));
}
TypeKind::Any => {
return Err(refuse(
"`any`, which DDS-XTypes has no encoding for; only CORBA CDR writes one, \
as a TypeCode ahead of the value",
));
}
TypeKind::ValueBase => {
return Err(refuse(
"`ValueBase`, which DDS-XTypes has no encoding for; only CORBA CDR writes \
one, as a chunked value with its repository id",
));
}
};
Ok(Some(value))
}
fn member_value(ast: &Ast, node: &MemberNode, type_: Id<TypeNode>) -> Result<Expr, EncodeError> {
match node.facets.xtypes.as_ref().and_then(|x| x.default_value) {
Some(expr) => explicit(ast, expr, type_, &node.name),
None => Ok(Expr::call(&defaulter_name(ast, type_), Vec::new())),
}
}
fn primitive_default(kind: PrimitiveKind) -> Expr {
match kind {
PrimitiveKind::Boolean => Expr::bool(false),
PrimitiveKind::Char | PrimitiveKind::WChar => Expr::Literal(Literal::Char('\0')),
PrimitiveKind::Float32 | PrimitiveKind::Float64 => {
Expr::Literal(Literal::Float("0".to_string()))
}
PrimitiveKind::Float128 => Expr::string("0"),
PrimitiveKind::Int64 | PrimitiveKind::UInt64 => Node::wide_signed(0),
PrimitiveKind::Octet
| PrimitiveKind::Int8
| PrimitiveKind::UInt8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32 => Expr::int(0),
}
}
fn selected_by_default(
ast: &Ast,
id: Id<TypeNode>,
selector: i128,
) -> Result<Option<(Id<TypeNode>, String)>, EncodeError> {
Ok(selected_case(ast, id, selector)?
.map(|(member, member_type)| (member_type, member.name.clone())))
}
fn discriminant_literal(ast: &Ast, discriminator: Id<TypeNode>, value: i128) -> Expr {
let resolved = resolve_alias(ast, discriminator);
if matches!(
ast.type_(resolved).map(|node| node.kind.clone()),
Ok(TypeKind::Enumeration { .. })
) {
if let Some(enumerator) = crate::emit::default_enumerator_name(ast, resolved) {
return Node::enumerator_constant(ast, resolved, &enumerator);
}
}
if matches!(
resolved_primitive(ast, resolved),
Some(PrimitiveKind::Boolean)
) {
return Expr::bool(value != 0);
}
if matches!(
resolved_primitive(ast, resolved),
Some(PrimitiveKind::Char | PrimitiveKind::WChar)
) {
let point = u32::try_from(value).ok().and_then(char::from_u32);
if let Some(ch) = point {
return Expr::Literal(Literal::Char(ch));
}
}
if matches!(
resolved_primitive(ast, resolved),
Some(PrimitiveKind::Int64 | PrimitiveKind::UInt64)
) {
return Node::wide_signed(value);
}
Expr::int(value)
}
fn resolved_primitive(ast: &Ast, id: Id<TypeNode>) -> Option<PrimitiveKind> {
let mut current = id;
for _ in 0..MAX_ALIAS_DEPTH {
match ast.type_(current).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => return Some(kind),
Ok(TypeKind::Alias { aliased }) => current = aliased.resolved()?,
_ => return None,
}
}
None
}
#[allow(clippy::too_many_lines)]
fn explicit(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
type_: Id<TypeNode>,
member_name: &str,
) -> Result<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_);
let kind = ast.type_(resolved).map(|node| node.kind.clone());
let is_float128 = matches!(kind, Ok(TypeKind::Primitive(PrimitiveKind::Float128)));
let is_wide = matches!(
kind,
Ok(TypeKind::Primitive(
PrimitiveKind::Int64 | PrimitiveKind::UInt64
))
);
match &node.expr {
ConstExpr::Literal(AstLiteral::Boolean(flag)) => Ok(Expr::bool(*flag)),
ConstExpr::Literal(AstLiteral::Char(ch)) => Ok(Expr::Literal(Literal::Char(*ch))),
ConstExpr::Literal(AstLiteral::WChar(code)) => {
let point = char::from_u32(*code)
.ok_or_else(|| refuse("a `@default` `wchar` that is not a Unicode scalar value"))?;
Ok(Expr::Literal(Literal::Char(point)))
}
ConstExpr::Literal(AstLiteral::String(value)) => Ok(Expr::string(value)),
ConstExpr::Literal(AstLiteral::WString(codes)) => {
let mut out = 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"));
};
out.push(ch);
}
Ok(Expr::string(&out))
}
ConstExpr::Literal(AstLiteral::Float { mantissa, exponent }) => {
let exponent = exponent
.as_ref()
.map_or(String::new(), |text| format!("e{text}"));
let written = format!("{mantissa}{exponent}");
if is_float128 {
return Ok(Expr::string(&written));
}
Ok(Expr::Literal(Literal::Float(written)))
}
ConstExpr::Literal(AstLiteral::Fixed { digits, scale }) => Ok(Expr::string(
&crate::adapter::constant::fixed_decimal(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(Node::enumerator_constant(ast, resolved, &enumerator.name))
}
_ => {
let value = ast
.eval_integral(expr)
.map_err(|_| refuse("a `@default` this frontend cannot evaluate"))?;
Ok(match kind {
Ok(TypeKind::Primitive(PrimitiveKind::Boolean)) => Expr::bool(value != 0),
Ok(TypeKind::Primitive(PrimitiveKind::Char | PrimitiveKind::WChar)) => {
let point = u32::try_from(value)
.ok()
.filter(|code| *code <= u32::from(u16::MAX))
.and_then(char::from_u32)
.ok_or_else(|| {
refuse("a `@default` character outside the range of a UTF-16 code unit")
})?;
Expr::Literal(Literal::Char(point))
}
Ok(TypeKind::Primitive(PrimitiveKind::Float128)) => {
Expr::string(&format!("{value}"))
}
_ if is_wide => Node::wide_signed(value),
_ => Expr::int(value),
})
}
}
}
fn calls_in_stat(statement: &Stat, out: &mut Vec<String>) {
match statement {
Stat::Declare { value, .. } => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
Stat::Assign { place, value, .. } => {
calls_in_expr(place, out);
calls_in_expr(value, out);
}
Stat::Function(function) => {
for inner in &function.body {
calls_in_stat(inner, out);
}
}
Stat::Return(value) => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
Stat::Expr(value) | Stat::Throw(value) => calls_in_expr(value, out),
Stat::If {
condition,
then,
otherwise,
} => {
calls_in_expr(condition, out);
for inner in then {
calls_in_stat(inner, out);
}
if let Some(rest) = otherwise {
for inner in rest {
calls_in_stat(inner, out);
}
}
}
Stat::While { condition, body } => {
calls_in_expr(condition, out);
for inner in body {
calls_in_stat(inner, out);
}
}
Stat::For { bound, body, .. } => {
calls_in_expr(bound, out);
for inner in body {
calls_in_stat(inner, out);
}
}
Stat::Break | Stat::Comment(_) | Stat::Verbatim(_) => {}
}
}
fn calls_in_expr(expr: &Expr, out: &mut Vec<String>) {
match expr {
Expr::Call { callee, args } => {
if let Expr::Name(name) = callee.as_ref() {
if name.as_str().starts_with("zd_def_")
&& !out.iter().any(|seen| seen == name.as_str())
{
out.push(name.as_str().to_string());
}
}
calls_in_expr(callee, out);
for arg in args {
calls_in_expr(arg, out);
}
}
Expr::Array(elements) => {
for element in elements {
calls_in_expr(element, out);
}
}
Expr::Object(fields) => {
for (_, value) in fields {
calls_in_expr(value, out);
}
}
Expr::New { args, .. } => {
for arg in args {
calls_in_expr(arg, out);
}
}
Expr::Member { object, .. } => calls_in_expr(object, out),
Expr::Index { object, index } => {
calls_in_expr(object, out);
calls_in_expr(index, out);
}
Expr::Paren(inner) | Expr::Unary { value: inner, .. } => calls_in_expr(inner, out),
Expr::Binary { lhs, rhs, .. } => {
calls_in_expr(lhs, out);
calls_in_expr(rhs, out);
}
Expr::Ternary {
condition,
then,
otherwise,
} => {
calls_in_expr(condition, out);
calls_in_expr(then, out);
calls_in_expr(otherwise, out);
}
Expr::Literal(_) | Expr::Undefined | Expr::Name(_) => {}
}
}