use crate::adapter::fsharp::{field_name, fsharp_type_name};
use crate::default_common::{discriminator_default_value, resolve_alias};
use crate::emit::default_enumerator_name;
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};
use zerodds_lang_cst::fsharp;
const MAX_ALIAS_DEPTH: usize = 64;
pub fn default_expr(ast: &Ast, id: Id<TypeNode>) -> Result<fsharp::Expr, EncodeError> {
default_expr_at(ast, id, 0)
}
fn default_expr_at(ast: &Ast, id: Id<TypeNode>, depth: usize) -> Result<fsharp::Expr, EncodeError> {
if depth > MAX_ALIAS_DEPTH {
return Err(EncodeError::NoWriter {
member: fsharp_type_name(ast, id),
kind: "type nested past the recursion bound",
});
}
let id = resolve_alias(ast, id);
let name = fsharp_type_name(ast, id);
let refuse = |kind: &'static str| EncodeError::NoWriter {
member: name.clone(),
kind,
};
let Ok(node) = ast.type_(id) else {
return Err(refuse("type that does not resolve"));
};
Ok(match &node.kind {
TypeKind::Primitive(kind) => primitive_default(*kind),
TypeKind::String { .. } => text(""),
TypeKind::Fixed { scale, .. } => text(&zero_decimal(*scale)),
TypeKind::Alias { .. } => return Err(refuse("typedef with an unresolved target")),
TypeKind::Enumeration { .. } => {
let enumerator = default_enumerator_name(ast, id)
.ok_or_else(|| refuse("enumeration with no enumerator to take as its default"))?;
enumerator_expr(ast, id, &enumerator)
}
TypeKind::Bitmask { .. } => {
let bound = ast.effective_bit_bound(id);
crate::adapter::fsharp::typed_int(
crate::adapter::fsharp::fsharp_holder(bound),
fsharp::Expr::int(0),
)
}
TypeKind::Bitset { .. } => fsharp::Expr::new(&name, Vec::new()),
TypeKind::Sequence { .. } | TypeKind::Map { .. } => fsharp::Expr::ArrayLit(Vec::new()),
TypeKind::Array { element, dims } => {
let element = element
.resolved()
.ok_or_else(|| 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"))?;
let length =
usize::try_from(length).map_err(|_| refuse("array of a negative length"))?;
lengths.push(length);
}
let inner = default_expr_at(ast, element, depth + 1)?;
let mut out = inner;
for length in lengths.into_iter().rev() {
out = fsharp::Expr::ArrayLit(core::iter::repeat_n(out.clone(), length).collect());
}
out
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
fsharp::Expr::new(&name, Vec::new())
}
TypeKind::Union { .. } => fsharp::Expr::new(&name, Vec::new()),
TypeKind::Native | TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
return Err(refuse("construct with no F# default value"));
}
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",
));
}
})
}
pub fn discriminator_default(
ast: &Ast,
discriminator: Id<TypeNode>,
) -> Result<fsharp::Expr, EncodeError> {
let resolved = resolve_alias(ast, discriminator);
let is_enum = matches!(
ast.type_(resolved).map(|node| node.kind.clone()),
Ok(TypeKind::Enumeration { .. })
);
if is_enum {
if let Some(enumerator) = default_enumerator_name(ast, resolved) {
return Ok(enumerator_expr(ast, resolved, &enumerator));
}
}
match discriminator_default_value(ast, resolved) {
Some(value) if resolved_primitive(ast, resolved) == Some(PrimitiveKind::Boolean) => {
Ok(fsharp::Expr::Literal(Literal::Bool(value != 0)))
}
Some(value) => Ok(match resolved_primitive(ast, resolved) {
Some(kind) => match crate::adapter::fsharp::fsharp_primitive_type(kind) {
fsharp::Type::Primitive(primitive) => {
crate::adapter::fsharp::typed_int(primitive, fsharp::Expr::int(value))
}
_ => fsharp::Expr::int(value),
},
None => fsharp::Expr::int(value),
}),
None => default_expr(ast, resolved),
}
}
pub fn member_value(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
) -> Result<fsharp::Expr, EncodeError> {
match node.facets.xtypes.as_ref().and_then(|x| x.default_value) {
Some(expr) => explicit(ast, expr, type_, &node.name),
None => Ok(fsharp::Expr::call(&defaulter_name(ast, type_), Vec::new())),
}
}
#[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_{}", crate::emit::fsharp::type_tag(ast, id, 0)),
}
}
#[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 fsharp_defaulters_over(
ast: &Ast,
types: &[Id<TypeNode>],
) -> Result<Vec<fsharp::Decl>, 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 returns = fsharp_value_type_or(ast, id, &name)?;
let value = default_expr(ast, id)?;
emitted.push(name.clone());
out.push(fsharp::Decl::Let(fsharp::LetDecl {
doc: Doc::line(
"Table 9 of DDS-XTypes 1.3: what this type is where the stream did not \
carry it. `unit -> T` so every call site gets a value of its own.",
),
name: Ident::new(&name),
params: vec![fsharp::FnParam {
name: Ident::new("_zd_unit"),
type_: fsharp::Type::Primitive(fsharp::Primitive::Unit),
}],
type_: Some(returns),
body: vec![fsharp::Stmt::Return(value)],
}));
}
Ok(out)
}
fn fsharp_value_type_or(
ast: &Ast,
id: Id<TypeNode>,
at: &str,
) -> Result<fsharp::Type, EncodeError> {
crate::adapter::fsharp::fsharp_value_type(ast, resolve_alias(ast, id), at).map_err(|_| {
EncodeError::NoWriter {
member: at.to_string(),
kind: "type with no F# form to return",
}
})
}
pub fn fsharp_defaulters_called_by(
ast: &Ast,
decls: &[fsharp::Decl],
) -> Result<Vec<fsharp::Decl>, 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_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 = fsharp_defaulters_over(ast, &wanted)?;
if !grew {
return Ok(produced);
}
for decl in &produced {
calls_in_decl(decl, &mut called);
}
}
fsharp_defaulters_over(ast, &wanted)
}
fn calls_in_decl(decl: &fsharp::Decl, out: &mut Vec<String>) {
if let fsharp::Decl::Let(let_decl) = decl {
calls_in_body(&let_decl.body, out);
}
}
fn calls_in_body(body: &[fsharp::Stmt], out: &mut Vec<String>) {
for statement in body {
calls_in_stmt(statement, out);
}
}
fn calls_in_stmt(statement: &fsharp::Stmt, out: &mut Vec<String>) {
match statement {
fsharp::Stmt::Let { value, .. } => calls_in_expr(value, out),
fsharp::Stmt::Assign { place, value } => {
calls_in_expr(place, out);
calls_in_expr(value, out);
}
fsharp::Stmt::Expr(value) | fsharp::Stmt::Return(value) => calls_in_expr(value, out),
fsharp::Stmt::If {
condition,
then,
otherwise,
} => {
calls_in_expr(condition, out);
calls_in_body(then, out);
if let Some(otherwise) = otherwise {
calls_in_body(otherwise, out);
}
}
fsharp::Stmt::CountUp { bound, body, .. } => {
calls_in_expr(bound, out);
calls_in_body(body, out);
}
fsharp::Stmt::Until { condition, body } => {
calls_in_expr(condition, out);
calls_in_body(body, out);
}
fsharp::Stmt::Guarded { body } => calls_in_body(body, out),
fsharp::Stmt::Leave | fsharp::Stmt::GiveBack | fsharp::Stmt::Comment(_) => {}
}
}
fn calls_in_expr(expr: &fsharp::Expr, out: &mut Vec<String>) {
match expr {
fsharp::Expr::Call { func, args } | fsharp::Expr::CurriedCall { func, args } => {
if let fsharp::Expr::Name(name) = func.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(func, out);
for arg in args {
calls_in_expr(arg, out);
}
}
fsharp::Expr::Binary { lhs, rhs, .. } => {
calls_in_expr(lhs, out);
calls_in_expr(rhs, out);
}
fsharp::Expr::Paren(value) | fsharp::Expr::Unary { value, .. } => {
calls_in_expr(value, out);
}
fsharp::Expr::Dot { value, .. } => calls_in_expr(value, out),
fsharp::Expr::Index { value, index } => {
calls_in_expr(value, out);
calls_in_expr(index, out);
}
fsharp::Expr::Some_(value) => calls_in_expr(value, out),
fsharp::Expr::New { args, .. } => {
for arg in args {
calls_in_expr(arg, out);
}
}
fsharp::Expr::ObjectInit { fields, .. } => {
for (_, value) in fields {
calls_in_expr(value, out);
}
}
fsharp::Expr::ArrayLit(elements) => {
for element in elements {
calls_in_expr(element, out);
}
}
fsharp::Expr::ArrayZeroCreate { count, .. } => calls_in_expr(count, out),
fsharp::Expr::Literal(_) | fsharp::Expr::Name(_) | fsharp::Expr::None_ => {}
}
}
fn primitive_default(kind: PrimitiveKind) -> fsharp::Expr {
match kind {
PrimitiveKind::Boolean => fsharp::Expr::Literal(Literal::Bool(false)),
PrimitiveKind::Float64 => fsharp::Expr::Literal(Literal::Float("0.0".to_string())),
PrimitiveKind::Float32 => fsharp::Expr::call(
"float32",
vec![fsharp::Expr::Literal(Literal::Float("0.0".to_string()))],
),
PrimitiveKind::Float128 => text("0"),
other => match crate::adapter::fsharp::fsharp_primitive_type(other) {
fsharp::Type::Primitive(primitive) => {
crate::adapter::fsharp::typed_int(primitive, fsharp::Expr::int(0))
}
_ => fsharp::Expr::int(0),
},
}
}
fn enumerator_expr(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> fsharp::Expr {
fsharp::Expr::dot(
fsharp::Expr::Name(Ident::new(&fsharp_type_name(ast, id))),
&field_name(enumerator),
)
}
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<fsharp::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(fsharp::Expr::Literal(Literal::Bool(*flag)))
}
ConstExpr::Literal(AstLiteral::Char(ch)) => Ok(crate::adapter::fsharp::typed_int(
fsharp::Primitive::Byte,
fsharp::Expr::int(i128::from(u32::from(*ch))),
)),
ConstExpr::Literal(AstLiteral::WChar(code)) => Ok(crate::adapter::fsharp::typed_int(
fsharp::Primitive::UInt16,
fsharp::Expr::int(i128::from(*code)),
)),
ConstExpr::Literal(AstLiteral::String(value)) => Ok(text(value)),
ConstExpr::Literal(AstLiteral::WString(codes)) => {
let mut out = String::new();
for code in codes {
let ch = char::from_u32(*code)
.ok_or_else(|| refuse("a `@default` wide string that is not code points"))?;
out.push(ch);
}
Ok(text(&out))
}
ConstExpr::Literal(AstLiteral::Float { mantissa, exponent }) => {
let exponent = exponent
.as_ref()
.map_or_else(String::new, |exponent| format!("e{exponent}"));
let literal = fsharp::Expr::Literal(Literal::Float(format!("{mantissa}{exponent}")));
if matches!(kind, Ok(TypeKind::Primitive(PrimitiveKind::Float32))) {
Ok(fsharp::Expr::call("float32", vec![literal]))
} else {
Ok(literal)
}
}
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_expr(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)) => {
fsharp::Expr::Literal(Literal::Bool(value != 0))
}
Ok(TypeKind::Primitive(PrimitiveKind::Char)) => {
if !(0..=0xFF).contains(&value) {
return Err(refuse("a `@default` character outside one octet"));
}
crate::adapter::fsharp::typed_int(
fsharp::Primitive::Byte,
fsharp::Expr::int(value),
)
}
Ok(TypeKind::Primitive(PrimitiveKind::WChar)) => {
if !(0..=0xFFFF).contains(&value) {
return Err(refuse(
"a `@default` character outside the range of a UTF-16 code unit",
));
}
crate::adapter::fsharp::typed_int(
fsharp::Primitive::UInt16,
fsharp::Expr::int(value),
)
}
Ok(TypeKind::Primitive(primitive)) => {
match crate::adapter::fsharp::fsharp_primitive_type(primitive) {
fsharp::Type::Primitive(target) => {
crate::adapter::fsharp::typed_int(target, fsharp::Expr::int(value))
}
_ => fsharp::Expr::int(value),
}
}
_ => fsharp::Expr::int(value),
})
}
}
}
fn text(value: &str) -> fsharp::Expr {
fsharp::Expr::Literal(Literal::Str(value.to_string()))
}