use crate::adapter::cst::MaterialiseError;
use crate::adapter::java::{declared_path, java_member_type, type_tag, unsigned_holder};
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::common::{Doc, Ident, Literal, Path};
use zerodds_lang_cst::java;
#[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_{}",
type_tag(ast, id, 0).unwrap_or_else(|_| format!("unresolved_type_{}", id.slot()))
),
}
}
#[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}"),
}
}
pub fn default_into(ast: &Ast, id: Id<TypeNode>, place: java::Expr) -> java::Statement {
assign(place, call(&defaulter_name(ast, id), Vec::new()))
}
pub fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: java::Expr,
) -> Result<Vec<java::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 java_defaulters_called_by(
ast: &Ast,
members: &[java::Member],
) -> Result<Vec<java::Member>, 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 member in members {
calls_in_member(member, &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 = java_defaulters_over(ast, &wanted)?;
if !grew {
return Ok(produced);
}
for member in &produced {
calls_in_member(member, &mut called);
}
}
java_defaulters_over(ast, &wanted)
}
pub fn java_defaulters_over(
ast: &Ast,
types: &[Id<TypeNode>],
) -> Result<Vec<java::Member>, 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(body) = body_of(ast, id, &name)? else {
continue;
};
emitted.push(name.clone());
out.push(java::Member::Method(java::Method {
doc: Doc::line(
"Table 9 of DDS-XTypes 1.3: what this type is where the stream did not \
carry it.",
),
annotations: Vec::new(),
modifiers: vec![java::Modifier::Static],
generics: Vec::new(),
returns: value_type(ast, id),
name: Ident::new(&name),
params: Vec::new(),
throws: Vec::new(),
body: Some(java::Block { statements: body }),
}));
}
Ok(out)
}
fn value_type(ast: &Ast, id: Id<TypeNode>) -> java::Type {
java_member_type(ast, id, "default").unwrap_or_else(|_| {
java::Type::named(Path::new(
&[format!("UnresolvedType{}", id.slot()).as_str()],
))
})
}
#[allow(clippy::too_many_lines)]
fn body_of(
ast: &Ast,
id: Id<TypeNode>,
own_name: &str,
) -> Result<Option<Vec<java::Statement>>, EncodeError> {
let refuse = |kind: &'static str| EncodeError::NoWriter {
member: own_name.to_string(),
kind,
};
let Ok(node) = ast.type_(id) else {
return Err(refuse("a type the model does not hold"));
};
let statements = match &node.kind {
TypeKind::Primitive(kind) => vec![give_back(primitive_default(*kind))],
TypeKind::String { .. } => vec![give_back(text(""))],
TypeKind::Fixed { scale, .. } => {
vec![give_back(big_decimal(&zero_decimal(*scale)))]
}
TypeKind::Alias { aliased } => {
let Some(target) = aliased.resolved() else {
return Err(refuse("typedef with an unresolved target"));
};
vec![give_back(call(&defaulter_name(ast, target), Vec::new()))]
}
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",
));
};
vec![give_back(enumerator_constant(ast, id, &enumerator))]
}
TypeKind::Bitmask { .. } => {
let holder = unsigned_holder(ast.effective_bit_bound(id));
vec![give_back(zero_of(&holder))]
}
TypeKind::Bitset { .. } => {
let mut statements = vec![new_local(value_type(ast, id))];
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", &java::escape_keyword(field_name)),
zero_of(&unsigned_holder(field_node.bits)),
));
}
}
statements.push(give_back(name("v")));
statements
}
TypeKind::Sequence { .. } | TypeKind::Map { .. } => match value_type(ast, id) {
java::Type::Named { path, args }
if args.is_empty()
&& path.segments().last().is_some_and(|last| {
last.as_str().starts_with(crate::adapter::java::MAP_PREFIX)
}) =>
{
vec![give_back(java::Expr::New {
type_: Box::new(java::Type::named(path)),
args: Vec::new(),
})]
}
java::Type::Array(element) => vec![give_back(new_array(
element,
vec![java::Expr::Literal(Literal::int(0))],
))],
java::Type::Named { args, .. }
if crate::adapter::java::mapping() == crate::adapter::java::JavaMapping::Psm
&& !args.is_empty() =>
{
vec![give_back(java::Expr::New {
type_: Box::new(java::Type::generic(
Path::new(crate::adapter::java::ARRAY_LIST),
args,
)),
args: Vec::new(),
})]
}
_ => {
return Err(refuse(
"collection whose Java form is neither an array nor a Collection",
));
}
},
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 {
lengths.push(
ast.eval_integral(*dim)
.map_err(|_| refuse("array whose length does not evaluate"))?,
);
}
let mut statements = vec![java::Statement::Declare {
final_: false,
type_: value_type(ast, id),
name: Ident::new("v"),
value: Some(new_array(
Box::new(value_type(ast, element)),
lengths
.iter()
.map(|length| java::Expr::Literal(Literal::int(*length)))
.collect(),
)),
}];
let mut access = name("v");
for level in 0..lengths.len() {
access = java::Expr::Index {
value: Box::new(access),
index: Box::new(name(&format!("zd_i{level}"))),
};
}
let mut inner = java::Block {
statements: vec![default_into(ast, element, access)],
};
for (level, length) in lengths.iter().enumerate().rev() {
inner = java::Block {
statements: vec![counted_loop(
&format!("zd_i{level}"),
java::Expr::Literal(Literal::int(*length)),
inner,
)],
};
}
statements.extend(inner.statements);
statements.push(give_back(name("v")));
statements
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let mut statements = vec![new_local(value_type(ast, id))];
for member_id in &ast.all_members_of(id) {
let Ok(member_node) = ast.member(*member_id) else {
continue;
};
let field = java::escape_keyword(&member_node.name);
if member_node.facets.is_optional() {
statements.push(assign(
member("v", &field),
java::Expr::Literal(Literal::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",
});
};
statements.extend(member_default(
ast,
member_node,
member_type,
member("v", &field),
)?);
}
statements.push(give_back(name("v")));
statements
}
TypeKind::Union { discriminator, .. } => {
let Some(discriminator) = discriminator.resolved() else {
return Err(refuse("union with an unresolved discriminator"));
};
let mut statements = vec![
new_local(value_type(ast, id)),
default_into(ast, discriminator, member("v", "_d")),
];
if let Some((member_type, field)) = selected_by_default(ast, id, discriminator)? {
statements.push(default_into(ast, member_type, member("v", &field)));
}
statements.push(give_back(name("v")));
statements
}
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(statements))
}
fn primitive_default(kind: PrimitiveKind) -> java::Expr {
match kind {
PrimitiveKind::Boolean => java::Expr::Literal(Literal::Bool(false)),
PrimitiveKind::Char | PrimitiveKind::WChar => {
java::Expr::Literal(Literal::Int("'\\0'".to_string()))
}
PrimitiveKind::Float32 => java::Expr::Literal(Literal::Float("0.0f".to_string())),
PrimitiveKind::Float64 => java::Expr::Literal(Literal::Float("0.0".to_string())),
PrimitiveKind::Float128 => java::Expr::Path(Path::new(&["Float128", "ZERO"])),
PrimitiveKind::Int8 | PrimitiveKind::Octet => java::Expr::Cast {
type_: Box::new(java::Type::Primitive(java::Primitive::Byte)),
value: Box::new(java::Expr::Literal(Literal::int(0))),
},
PrimitiveKind::Int16 => java::Expr::Cast {
type_: Box::new(java::Type::Primitive(java::Primitive::Short)),
value: Box::new(java::Expr::Literal(Literal::int(0))),
},
PrimitiveKind::Int64 | PrimitiveKind::UInt32 | PrimitiveKind::UInt64 => {
java::Expr::Literal(Literal::Int("0L".to_string()))
}
PrimitiveKind::UInt8 | PrimitiveKind::UInt16 | PrimitiveKind::Int32 => {
java::Expr::Literal(Literal::int(0))
}
}
}
fn zero_of(holder: &java::Type) -> java::Expr {
if *holder == java::Type::Primitive(java::Primitive::Long) {
return java::Expr::Literal(Literal::Int("0L".to_string()));
}
java::Expr::Literal(Literal::int(0))
}
pub(crate) fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> java::Expr {
let owner = declared_path(ast, id).unwrap_or_else(|_| {
format!("UnresolvedEnum{}", id.slot())
});
java::Expr::Path(Path::new(
&format!("{owner}.{}", java::escape_keyword(enumerator))
.split('.')
.collect::<Vec<_>>(),
))
}
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, java::escape_keyword(&member.name))))
}
#[allow(clippy::too_many_lines)]
fn explicit(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
type_: Id<TypeNode>,
member_name: &str,
) -> Result<java::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());
match &node.expr {
ConstExpr::Literal(AstLiteral::Boolean(flag)) => {
Ok(java::Expr::Literal(Literal::Bool(*flag)))
}
ConstExpr::Literal(AstLiteral::Char(ch)) => Ok(char_literal(u32::from(*ch))),
ConstExpr::Literal(AstLiteral::WChar(code)) => Ok(char_literal(*code)),
ConstExpr::Literal(AstLiteral::String(value)) => Ok(text(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(text(&out))
}
ConstExpr::Literal(AstLiteral::Float { mantissa, exponent }) => {
let suffix = match kind {
Ok(TypeKind::Primitive(PrimitiveKind::Float32)) => "f",
_ => "",
};
let exponent = exponent
.as_ref()
.map_or(String::new(), |text| format!("e{text}"));
Ok(java::Expr::Literal(Literal::Float(format!(
"{mantissa}{exponent}{suffix}"
))))
}
ConstExpr::Literal(AstLiteral::Fixed { digits, scale }) => {
Ok(big_decimal(&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(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::Char | PrimitiveKind::WChar)) => {
char_literal(u32::try_from(value).unwrap_or(0))
}
Ok(TypeKind::Primitive(PrimitiveKind::Int8)) => java::Expr::Cast {
type_: Box::new(java::Type::Primitive(java::Primitive::Byte)),
value: Box::new(java::Expr::Literal(Literal::int(value))),
},
Ok(TypeKind::Primitive(PrimitiveKind::Int16)) => java::Expr::Cast {
type_: Box::new(java::Type::Primitive(java::Primitive::Short)),
value: Box::new(java::Expr::Literal(Literal::int(value))),
},
Ok(TypeKind::Primitive(
PrimitiveKind::Int64 | PrimitiveKind::UInt32 | PrimitiveKind::UInt64,
)) => wide(value),
_ => java::Expr::Literal(Literal::int(value)),
})
}
}
}
fn wide(value: i128) -> java::Expr {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let pattern = if value > i128::from(i64::MAX) {
value as u64 as i64
} else {
value as i64
};
java::Expr::Literal(Literal::Int(format!("{pattern}L")))
}
fn char_literal(code: u32) -> java::Expr {
java::Expr::Cast {
type_: Box::new(java::Type::Primitive(java::Primitive::Char)),
value: Box::new(java::Expr::Literal(Literal::int(i128::from(code)))),
}
}
fn big_decimal(decimal: &str) -> java::Expr {
java::Expr::New {
type_: Box::new(java::Type::named(Path::new(&[
"java",
"math",
"BigDecimal",
]))),
args: vec![text(decimal)],
}
}
fn calls_in_member(member: &java::Member, out: &mut Vec<String>) {
match member {
java::Member::Method(method) => {
if let Some(body) = &method.body {
calls_in_block(body, out);
}
}
java::Member::Field(field) => {
if let Some(value) = &field.value {
calls_in_expr(value, out);
}
}
java::Member::Constructor(constructor) => calls_in_block(&constructor.body, out),
java::Member::Initialiser { body, .. } => calls_in_block(body, out),
java::Member::Nested(_) | java::Member::Comment(_) | java::Member::Verbatim(_) => {}
}
}
fn calls_in_block(block: &java::Block, out: &mut Vec<String>) {
for statement in &block.statements {
calls_in_statement(statement, out);
}
}
#[allow(clippy::too_many_lines)]
fn calls_in_statement(statement: &java::Statement, out: &mut Vec<String>) {
match statement {
java::Statement::Declare { value, .. } => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
java::Statement::Expr(expr) | java::Statement::Throw(expr) => calls_in_expr(expr, out),
java::Statement::Return(value) => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
java::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);
}
}
java::Statement::ForEach { range, body, .. } => {
calls_in_expr(range, out);
calls_in_block(body, out);
}
java::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);
}
java::Statement::While { condition, body } => {
calls_in_expr(condition, out);
calls_in_block(body, out);
}
java::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);
}
}
java::Statement::Break | java::Statement::Comment(_) => {}
}
}
#[allow(clippy::too_many_lines)]
fn calls_in_expr(expr: &java::Expr, out: &mut Vec<String>) {
match expr {
java::Expr::MethodCall {
receiver,
name,
args,
} => {
if receiver.is_none() {
let name = name.as_str();
if name.starts_with("zd_def_") && !out.iter().any(|seen| seen == name) {
out.push(name.to_string());
}
}
if let Some(receiver) = receiver {
calls_in_expr(receiver, out);
}
for arg in args {
calls_in_expr(arg, out);
}
}
java::Expr::Field { value, .. }
| java::Expr::Cast { value, .. }
| java::Expr::InstanceOf { value, .. }
| java::Expr::Paren(value) => calls_in_expr(value, out),
java::Expr::Index { value, index } => {
calls_in_expr(value, out);
calls_in_expr(index, out);
}
java::Expr::New { args, .. } => {
for arg in args {
calls_in_expr(arg, out);
}
}
java::Expr::NewArray {
lengths, elements, ..
} => {
for length in lengths {
calls_in_expr(length, out);
}
for element in elements {
calls_in_expr(element, out);
}
}
java::Expr::Binary { lhs, rhs, .. } => {
calls_in_expr(lhs, out);
calls_in_expr(rhs, out);
}
java::Expr::Ternary {
condition,
then,
otherwise,
} => {
calls_in_expr(condition, out);
calls_in_expr(then, out);
calls_in_expr(otherwise, out);
}
java::Expr::Lambda { body, .. } => calls_in_expr(body, out),
java::Expr::ArrayInit(elements) => {
for element in elements {
calls_in_expr(element, out);
}
}
java::Expr::Literal(_)
| java::Expr::Path(_)
| java::Expr::This
| java::Expr::Annotation(_) => {}
}
}
fn name(text: &str) -> java::Expr {
java::Expr::Path(Path::new(&text.split('.').collect::<Vec<_>>()))
}
fn text(value: &str) -> java::Expr {
java::Expr::Literal(Literal::Str(value.to_string()))
}
fn call(callee: &str, args: Vec<java::Expr>) -> java::Expr {
java::Expr::MethodCall {
receiver: None,
name: Ident::new(callee),
args,
}
}
fn member(base: &str, field: &str) -> java::Expr {
if crate::adapter::java::mapping() == crate::adapter::java::JavaMapping::Psm {
return crate::adapter::java::accessor(name(base), field);
}
java::Expr::Field {
value: Box::new(name(base)),
name: Ident::new(field),
}
}
fn assign(place: java::Expr, value: java::Expr) -> java::Statement {
crate::adapter::java::assign(place, value)
}
fn new_array(element: Box<java::Type>, lengths: Vec<java::Expr>) -> java::Expr {
let mut innermost = &*element;
while let java::Type::Array(inner) = innermost {
innermost = inner;
}
if let java::Type::Named { path, args } = innermost {
if !args.is_empty() {
let mut segments: Vec<&str> = path
.segments()
.iter()
.map(|segment| segment.as_str())
.collect();
segments.push("class");
let mut arguments = vec![java::Expr::Path(Path::new(&segments))];
arguments.extend(lengths);
return java::Expr::MethodCall {
receiver: Some(Box::new(java::Expr::Path(Path::new(&[
crate::adapter::java::SEQUENCES,
])))),
name: Ident::new("newArray"),
args: arguments,
};
}
}
java::Expr::NewArray {
element,
lengths,
elements: Vec::new(),
}
}
fn give_back(value: java::Expr) -> java::Statement {
java::Statement::Return(Some(value))
}
fn new_local(type_: java::Type) -> java::Statement {
java::Statement::Declare {
final_: false,
type_: type_.clone(),
name: Ident::new("v"),
value: Some(java::Expr::New {
type_: Box::new(type_),
args: Vec::new(),
}),
}
}
fn counted_loop(index: &str, count: java::Expr, body: java::Block) -> java::Statement {
java::Statement::For {
init: Box::new(java::Statement::Declare {
final_: false,
type_: java::Type::Primitive(java::Primitive::Int),
name: Ident::new(index),
value: Some(java::Expr::Literal(Literal::int(0))),
}),
condition: java::Expr::Binary {
op: "<",
lhs: Box::new(name(index)),
rhs: Box::new(count),
},
step: java::Expr::Binary {
op: "=",
lhs: Box::new(name(index)),
rhs: Box::new(java::Expr::Binary {
op: "+",
lhs: Box::new(name(index)),
rhs: Box::new(java::Expr::Literal(Literal::int(1))),
}),
},
body,
}
}
const _: Option<MaterialiseError> = None;