use crate::adapter::csharp::{csharp_member_type, declared_path, field_name, type_tag};
use crate::adapter::cst::MaterialiseError;
use crate::default_common::{discriminator_default_value, resolve_alias, selected_case};
use crate::emit::csharp::CSharp;
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::csharp;
#[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: csharp::Expr) -> csharp::Statement {
assign(place, call(&defaulter_name(ast, id), Vec::new()))
}
pub fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: csharp::Expr,
) -> Result<Vec<csharp::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 csharp_defaulters_called_by(
ast: &Ast,
members: &[csharp::Member],
) -> Result<Vec<csharp::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 = csharp_defaulters_over(ast, &wanted)?;
if !grew {
return Ok(produced);
}
for member in &produced {
calls_in_member(member, &mut called);
}
}
csharp_defaulters_over(ast, &wanted)
}
pub fn csharp_defaulters_over(
ast: &Ast,
types: &[Id<TypeNode>],
) -> Result<Vec<csharp::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(csharp::Member::Method(csharp::Method {
doc: Doc::line(
"Table 9 of DDS-XTypes 1.3: what this type is where the stream did not \
carry it.",
),
attributes: Vec::new(),
modifiers: vec![csharp::Modifier::Private, csharp::Modifier::Static],
returns: value_type(ast, id),
name: Ident::new(&name),
generics: Vec::new(),
params: Vec::new(),
constraints: Vec::new(),
body: Some(csharp::Block { statements: body }),
expression: None,
}));
}
Ok(out)
}
fn value_type(ast: &Ast, id: Id<TypeNode>) -> csharp::Type {
csharp_member_type(ast, id, "default").unwrap_or_else(|_| {
csharp::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<csharp::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(text(&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 { .. } => vec![give_back(csharp::Expr::Cast {
type_: Box::new(value_type(ast, id)),
value: Box::new(csharp::Expr::Literal(Literal::int(0))),
})],
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(name) = field_node.name.as_ref() {
statements.push(assign(
member("v", &field_name(name)),
csharp::Expr::Literal(Literal::int(0)),
));
}
}
statements.push(give_back(name("v")));
statements
}
TypeKind::Sequence { .. } | TypeKind::Map { .. } => {
let csharp::Type::Array { .. } = value_type(ast, id) else {
return Err(refuse("collection whose C# form is not an array"));
};
let (element, nested) = CSharp::peel(value_type(ast, id));
vec![give_back(csharp::Expr::NewArray {
element: Box::new(element),
length: Some(Box::new(csharp::Expr::Literal(Literal::int(0)))),
elements: Vec::new(),
nested: nested.saturating_sub(1),
})]
}
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 (innermost, total) = CSharp::peel(value_type(ast, id));
let ranks = lengths.len();
let mut statements = vec![csharp::Statement::Declare {
type_: value_type(ast, id),
name: Ident::new("v"),
value: Some(csharp::Expr::NewArray {
element: Box::new(innermost.clone()),
length: Some(Box::new(csharp::Expr::Literal(Literal::int(lengths[0])))),
elements: Vec::new(),
nested: total - 1,
}),
}];
let place = |depth: usize| {
let mut access = name("v");
for level in 0..=depth {
access = csharp::Expr::Index {
value: Box::new(access),
index: Box::new(name(&format!("zd_i{level}"))),
};
}
access
};
let mut inner = vec![default_into(ast, element, place(ranks - 1))];
for level in (0..ranks).rev() {
let mut body = Vec::new();
if level + 1 < ranks {
body.push(assign(
place(level),
csharp::Expr::NewArray {
element: Box::new(innermost.clone()),
length: Some(Box::new(csharp::Expr::Literal(Literal::int(
lengths[level + 1],
)))),
elements: Vec::new(),
nested: total - level - 2,
},
));
}
body.extend(inner);
inner = vec![counted_loop(
&format!("zd_i{level}"),
csharp::Expr::Literal(Literal::int(lengths[level])),
csharp::Block { statements: body },
)];
}
statements.extend(inner);
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 = field_name(&member_node.name);
if member_node.facets.is_optional() {
statements.push(assign(
member("v", &field),
csharp::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", &field_name("_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) -> csharp::Expr {
match kind {
PrimitiveKind::Boolean => csharp::Expr::Literal(Literal::Bool(false)),
PrimitiveKind::Char | PrimitiveKind::WChar => csharp::Expr::Literal(Literal::Char('\0')),
PrimitiveKind::Float32 => csharp::Expr::Literal(Literal::Float("0.0f".to_string())),
PrimitiveKind::Float64 => csharp::Expr::Literal(Literal::Float("0.0".to_string())),
PrimitiveKind::Float128 => csharp::Expr::Member {
value: Box::new(csharp::Expr::Path(Path::rooted(&[
"ZeroDds", "Idl", "Float128",
]))),
name: Ident::new("Zero"),
},
PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::Int32
| PrimitiveKind::Int64
| PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::UInt16
| PrimitiveKind::UInt32
| PrimitiveKind::UInt64 => csharp::Expr::Literal(Literal::int(0)),
}
}
pub(crate) fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> csharp::Expr {
let owner = declared_path(ast, id).unwrap_or_else(|_| {
format!("UnresolvedEnum{}", id.slot())
});
let name = zerodds_lang_cst::csharp::escape_keyword(enumerator);
csharp::Expr::Member {
value: Box::new(path(&owner)),
name: Ident::new(&name),
}
}
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, field_name(&member.name))))
}
#[allow(clippy::too_many_lines)]
fn explicit(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
type_: Id<TypeNode>,
member_name: &str,
) -> Result<csharp::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(csharp::Expr::Literal(Literal::Bool(*flag)))
}
ConstExpr::Literal(AstLiteral::Char(ch)) => char_literal(u32::from(*ch), refuse),
ConstExpr::Literal(AstLiteral::WChar(code)) => char_literal(*code, refuse),
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(csharp::Expr::Literal(Literal::Float(format!(
"{mantissa}{exponent}{suffix}"
))))
}
ConstExpr::Literal(AstLiteral::Fixed { digits, scale }) => {
Ok(text(&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)) => {
let code = u32::try_from(value).map_err(|_| {
refuse("a `@default` character outside the range of a code unit")
})?;
char_literal(code, refuse)?
}
Ok(TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. }) => csharp::Expr::Cast {
type_: Box::new(value_type(ast, resolved)),
value: Box::new(csharp::Expr::Literal(Literal::int(value))),
},
_ => csharp::Expr::Literal(Literal::int(value)),
})
}
}
}
fn char_literal(
code: u32,
refuse: impl Fn(&'static str) -> EncodeError,
) -> Result<csharp::Expr, EncodeError> {
if code > u32::from(u16::MAX) {
return Err(refuse(
"a `@default` character above the Basic Multilingual Plane, \
which no single C# `char` holds",
));
}
Ok(csharp::Expr::Cast {
type_: Box::new(csharp::Type::Predefined(csharp::Predefined::Char)),
value: Box::new(csharp::Expr::Literal(Literal::int(i128::from(code)))),
})
}
fn calls_in_member(member: &csharp::Member, out: &mut Vec<String>) {
match member {
csharp::Member::Method(method) => {
if let Some(body) = &method.body {
calls_in_block(body, out);
}
if let Some(value) = &method.expression {
calls_in_expr(value, out);
}
}
csharp::Member::Field(field) => {
if let Some(value) = &field.value {
calls_in_expr(value, out);
}
}
csharp::Member::Property(property) => {
if let Some(value) = &property.expression {
calls_in_expr(value, out);
}
if let Some(value) = &property.initialiser {
calls_in_expr(value, out);
}
}
csharp::Member::Constructor(constructor) => calls_in_block(&constructor.body, out),
csharp::Member::Nested(_) | csharp::Member::Comment(_) | csharp::Member::Verbatim(_) => {}
}
}
fn calls_in_block(block: &csharp::Block, out: &mut Vec<String>) {
for statement in &block.statements {
calls_in_statement(statement, out);
}
}
fn calls_in_statement(statement: &csharp::Statement, out: &mut Vec<String>) {
match statement {
csharp::Statement::Declare { value, .. } => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
csharp::Statement::Expr(expr) | csharp::Statement::Throw(expr) => {
calls_in_expr(expr, out);
}
csharp::Statement::Return(value) => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
csharp::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);
}
}
csharp::Statement::ForEach { range, body, .. } => {
calls_in_expr(range, out);
calls_in_block(body, out);
}
csharp::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);
}
csharp::Statement::While { condition, body } => {
calls_in_expr(condition, out);
calls_in_block(body, out);
}
csharp::Statement::Switch { value, sections } => {
calls_in_expr(value, out);
for section in sections {
for label in §ion.labels {
calls_in_expr(label, out);
}
calls_in_block(§ion.body, out);
}
}
csharp::Statement::Using { resource, body } => {
calls_in_statement(resource, out);
calls_in_block(body, out);
}
csharp::Statement::Block(block) => calls_in_block(block, out),
csharp::Statement::Break | csharp::Statement::Comment(_) => {}
}
}
#[allow(clippy::too_many_lines)]
fn calls_in_expr(expr: &csharp::Expr, out: &mut Vec<String>) {
match expr {
csharp::Expr::Invoke {
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);
}
}
csharp::Expr::Member { value, .. }
| csharp::Expr::Cast { value, .. }
| csharp::Expr::Is { value, .. }
| csharp::Expr::Unary { value, .. }
| csharp::Expr::NameOf(value)
| csharp::Expr::Paren(value) => calls_in_expr(value, out),
csharp::Expr::Index { value, index } => {
calls_in_expr(value, out);
calls_in_expr(index, out);
}
csharp::Expr::New {
args, initialiser, ..
} => {
for arg in args {
calls_in_expr(arg, out);
}
for (_, value) in initialiser {
calls_in_expr(value, out);
}
}
csharp::Expr::NewArray {
length, elements, ..
} => {
if let Some(length) = length {
calls_in_expr(length, out);
}
for element in elements {
calls_in_expr(element, out);
}
}
csharp::Expr::Binary { lhs, rhs, .. } => {
calls_in_expr(lhs, out);
calls_in_expr(rhs, out);
}
csharp::Expr::Assign { target, value } => {
calls_in_expr(target, out);
calls_in_expr(value, out);
}
csharp::Expr::Conditional {
condition,
then,
otherwise,
} => {
calls_in_expr(condition, out);
calls_in_expr(then, out);
calls_in_expr(otherwise, out);
}
csharp::Expr::Lambda { body, .. } => calls_in_expr(body, out),
csharp::Expr::Literal(_)
| csharp::Expr::Path(_)
| csharp::Expr::This
| csharp::Expr::Default(_)
| csharp::Expr::TypeOf(_) => {}
}
}
fn path(text: &str) -> csharp::Expr {
csharp::Expr::Path(match text.strip_prefix("global::") {
Some(rest) => Path::rooted(&rest.split('.').collect::<Vec<_>>()),
None => Path::new(&text.split('.').collect::<Vec<_>>()),
})
}
fn name(text: &str) -> csharp::Expr {
path(text)
}
fn text(value: &str) -> csharp::Expr {
csharp::Expr::Literal(Literal::Str(value.to_string()))
}
fn call(callee: &str, args: Vec<csharp::Expr>) -> csharp::Expr {
csharp::Expr::Invoke {
receiver: None,
name: Ident::new(callee),
args,
}
}
fn member(base: &str, field: &str) -> csharp::Expr {
csharp::Expr::Member {
value: Box::new(name(base)),
name: Ident::new(field),
}
}
fn assign(place: csharp::Expr, value: csharp::Expr) -> csharp::Statement {
csharp::Statement::Expr(csharp::Expr::Binary {
op: "=",
lhs: Box::new(place),
rhs: Box::new(value),
})
}
fn give_back(value: csharp::Expr) -> csharp::Statement {
csharp::Statement::Return(Some(value))
}
fn new_local(type_: csharp::Type) -> csharp::Statement {
csharp::Statement::Declare {
type_: type_.clone(),
name: Ident::new("v"),
value: Some(csharp::Expr::New {
type_: Box::new(type_),
args: Vec::new(),
initialiser: Vec::new(),
}),
}
}
fn counted_loop(index: &str, count: csharp::Expr, body: csharp::Block) -> csharp::Statement {
csharp::Statement::For {
init: Box::new(csharp::Statement::Declare {
type_: csharp::Type::Predefined(csharp::Predefined::Int),
name: Ident::new(index),
value: Some(csharp::Expr::Literal(Literal::int(0))),
}),
condition: csharp::Expr::Binary {
op: "<",
lhs: Box::new(name(index)),
rhs: Box::new(count),
},
step: csharp::Expr::Binary {
op: "=",
lhs: Box::new(name(index)),
rhs: Box::new(csharp::Expr::Binary {
op: "+",
lhs: Box::new(name(index)),
rhs: Box::new(csharp::Expr::Literal(Literal::int(1))),
}),
},
body,
}
}
const _: Option<MaterialiseError> = None;