use crate::default_common::{discriminator_default_value, resolve_alias};
use crate::encode::EncodeError;
use crate::fixed_literal::fixed_text;
use zerodds_ast::{
Ast, ConstExpr, Literal as AstLiteral, MemberNode, PrimitiveKind, TypeKind, TypeNode,
};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Ident, Literal, Path};
use zerodds_lang_cst::rust;
const MAX_DEPTH: usize = 64;
pub fn member_default(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
node: &MemberNode,
type_: Id<TypeNode>,
) -> Result<rust::Expr, EncodeError> {
if node.facets.is_optional() {
return Ok(rust::Expr::Path(Path::single("None")));
}
let value = member_value_default(ast, from, node, type_)?;
Ok(crate::adapter::cst::external_value(ast, node, value))
}
pub(crate) fn member_value_default(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
node: &MemberNode,
type_: Id<TypeNode>,
) -> Result<rust::Expr, EncodeError> {
match node.facets.xtypes.as_ref().and_then(|x| x.default_value) {
Some(expr) => explicit(ast, expr, type_, &node.name),
None => default_of(ast, from, type_, &node.name),
}
}
pub fn default_of(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
type_: Id<TypeNode>,
at: &str,
) -> Result<rust::Expr, EncodeError> {
at_depth(ast, from, type_, at, 0)
}
#[allow(clippy::too_many_lines)]
fn at_depth(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
type_: Id<TypeNode>,
at: &str,
depth: usize,
) -> Result<rust::Expr, EncodeError> {
let refuse = |kind: &'static str| EncodeError::NoWriter {
member: at.to_string(),
kind,
};
if depth > MAX_DEPTH {
return Err(refuse(
"type whose default nests deeper than an acyclic model can",
));
}
let node = ast.type_(type_).map_err(|error| EncodeError::NoLayout {
detail: format!("{error}"),
})?;
Ok(match &node.kind {
TypeKind::Primitive(kind) => primitive_default(*kind),
TypeKind::String { .. } => call_path(&["String", "new"]),
TypeKind::Fixed { scale, .. } => {
let text = if *scale == 0 {
String::from("0")
} else {
format!("0.{}", "0".repeat(usize::from(*scale)))
};
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Literal(Literal::string(&text))),
method: Ident::new("to_string"),
args: Vec::new(),
}
}
TypeKind::Alias { aliased } => {
let target = aliased
.resolved()
.ok_or_else(|| refuse("unresolved typedef"))?;
at_depth(ast, from, target, at, depth + 1)?
}
TypeKind::Enumeration { .. } => {
let variant = default_enumerator(ast, type_)
.ok_or_else(|| refuse("enumeration with no enumerator to take as its default"))?;
let path = crate::adapter::cst::relative_path(ast, from, node)
.map_err(|_| refuse("an enumeration the model has no module for"))?;
let mut segments: Vec<String> = path
.segments()
.iter()
.map(|s| s.as_str().to_string())
.collect();
segments.push(variant.clone());
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
rust::Expr::Path(Path::new(&borrowed))
}
TypeKind::Bitmask { .. } => rust::Expr::Literal(Literal::int(0)),
TypeKind::Bitset { .. } => {
let mut fields = Vec::new();
for field in &ast.all_bitfields_of(type_) {
let field_node = ast
.bitfield(*field)
.map_err(|_| refuse("bitset with a stale bitfield handle"))?;
let Some(field_name) = field_node.name.as_ref() else {
continue;
};
fields.push((
Ident::new(&rust::escape_keyword(field_name)),
rust::Expr::Literal(Literal::int(0)),
));
}
rust::Expr::StructLit {
path: crate::adapter::cst::relative_path(ast, from, node)
.map_err(|_| refuse("a bitset the model has no module for"))?,
fields,
base: None,
}
}
TypeKind::Sequence { .. } => call_path(&["Vec", "new"]),
TypeKind::Map { .. } => match crate::adapter::cst::rust_map_form() {
crate::adapter::cst::RustMapForm::BTreeMap => call_path(&["BTreeMap", "new"]),
crate::adapter::cst::RustMapForm::Entries => call_path(&["Vec", "new"]),
},
TypeKind::Array { element, dims } => {
let element = element
.resolved()
.ok_or_else(|| refuse("unresolved array element"))?;
let mut expression = at_depth(ast, from, element, at, depth + 1)?;
let copyable = matches!(
ast.type_(element).ok().map(|node| &node.kind),
Some(TypeKind::Primitive(_))
);
for dim in dims.iter().rev() {
let count = ast
.eval_integral(*dim)
.map_err(|_| refuse("array whose length does not evaluate"))?;
expression = if copyable {
rust::Expr::Repeat {
value: Box::new(expression),
count: Box::new(rust::Expr::Literal(Literal::int(count))),
}
} else {
let count = usize::try_from(count)
.map_err(|_| refuse("array whose length does not fit a usize"))?;
rust::Expr::Array(vec![expression.clone(); count])
};
}
expression
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let mut fields = Vec::new();
if let Some(base) = crate::adapter::c::struct_base(ast, type_) {
fields.push((
Ident::new(crate::adapter::cst::PARENT),
at_depth(ast, from, base, crate::adapter::cst::PARENT, depth + 1)?,
));
}
for member in ast.members_of(type_) {
let Ok(member_node) = ast.member(*member) else {
continue;
};
let member_type = member_node
.type_
.resolved()
.ok_or_else(|| refuse("member with an unresolved type"))?;
let value = if member_node.facets.is_optional() {
rust::Expr::Path(Path::single("None"))
} else {
let value = match member_node
.facets
.xtypes
.as_ref()
.and_then(|x| x.default_value)
{
Some(expr) => explicit(ast, expr, member_type, &member_node.name)?,
None => at_depth(ast, from, member_type, &member_node.name, depth + 1)?,
};
crate::adapter::cst::external_value(ast, member_node, value)
};
fields.push((Ident::new(&rust::escape_keyword(&member_node.name)), value));
}
rust::Expr::StructLit {
path: crate::adapter::cst::relative_path(ast, from, node)
.map_err(|_| refuse("a struct the model has no module for"))?,
fields,
base: None,
}
}
TypeKind::Union { .. } => {
let shape = crate::rust_union::shape(ast, type_).map_err(refuse)?;
let selector = discriminator_default_value(ast, shape.discriminator)
.ok_or_else(|| refuse("union whose discriminator has no default value"))?;
let discriminator_value =
crate::rust_union::label_literal(ast, from, shape.kind, selector)
.map_err(refuse)?
.expr();
let path = crate::adapter::cst::relative_path(ast, from, node)
.map_err(|_| refuse("a union the model has no module for"))?;
let mut segments: Vec<String> = path
.segments()
.iter()
.map(|s| s.as_str().to_string())
.collect();
let selected = shape
.cases
.iter()
.find(|case| case.labels.contains(&selector))
.or_else(|| shape.cases.iter().find(|case| case.is_default));
let args = match selected {
Some(case) => {
let member = case.member;
let member_type = member
.type_
.resolved()
.ok_or_else(|| refuse("union case with an unresolved type"))?;
segments.push(case.variant.clone());
let mut args = Vec::new();
if case.carries_discriminator() {
args.push(discriminator_value);
}
args.push(crate::adapter::cst::external_value(
ast,
member,
at_depth(ast, from, member_type, &member.name, depth + 1)?,
));
args
}
None => {
segments.push(crate::rust_union::NO_MEMBER.to_string());
vec![discriminator_value]
}
};
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&borrowed))),
args,
}
}
other => {
let _ = other;
return Err(refuse("type with no default value in this column"));
}
})
}
fn primitive_default(kind: PrimitiveKind) -> rust::Expr {
match kind {
PrimitiveKind::Boolean => rust::Expr::Literal(Literal::Bool(false)),
PrimitiveKind::Char | PrimitiveKind::WChar => rust::Expr::Literal(Literal::Char('\0')),
PrimitiveKind::Float32 => rust::Expr::Literal(Literal::Float(String::from("0.0f32"))),
PrimitiveKind::Float64 => rust::Expr::Literal(Literal::Float(String::from("0.0f64"))),
PrimitiveKind::Float128 => rust::Expr::Path(Path::new(&["Float128", "ZERO"])),
PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
| PrimitiveKind::Int64
| PrimitiveKind::UInt64 => rust::Expr::Literal(Literal::int(0)),
}
}
fn default_enumerator(ast: &Ast, type_: Id<TypeNode>) -> Option<String> {
let mut first = None;
for enumerator in ast.enumerators_of(type_) {
let Ok(node) = ast.enumerator(*enumerator) else {
continue;
};
let variant = rust::escape_keyword(&node.name);
if node
.facets
.xtypes
.as_ref()
.is_some_and(|x| x.is_default_literal)
{
return Some(variant);
}
if first.is_none() {
first = Some(variant);
}
}
first
}
fn explicit(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
type_: Id<TypeNode>,
member_name: &str,
) -> Result<rust::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_);
Ok(match &node.expr {
ConstExpr::Literal(AstLiteral::Boolean(flag)) => rust::Expr::Literal(Literal::Bool(*flag)),
ConstExpr::Literal(AstLiteral::Char(ch)) => rust::Expr::Literal(Literal::Char(*ch)),
ConstExpr::Literal(AstLiteral::WChar(code)) => {
let ch = char::from_u32(*code)
.ok_or_else(|| refuse("a `@default` wide character that is not a code point"))?;
rust::Expr::Literal(Literal::Char(ch))
}
ConstExpr::Literal(AstLiteral::String(text)) => rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Literal(Literal::string(text))),
method: Ident::new("to_string"),
args: Vec::new(),
},
ConstExpr::Literal(AstLiteral::WString(codes)) => {
let mut text = 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"))?;
text.push(ch);
}
rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Literal(Literal::string(&text))),
method: Ident::new("to_string"),
args: Vec::new(),
}
}
ConstExpr::Literal(AstLiteral::Float {
mantissa,
exponent: None,
}) => rust::Expr::Literal(Literal::Float(mantissa.clone())),
ConstExpr::Literal(AstLiteral::Float {
mantissa,
exponent: Some(exponent),
}) => rust::Expr::Literal(Literal::Float(format!("{mantissa}e{exponent}"))),
ConstExpr::Literal(AstLiteral::Fixed { digits, scale }) => rust::Expr::MethodCall {
receiver: Box::new(rust::Expr::Literal(Literal::string(&fixed_text(
digits, *scale,
)))),
method: Ident::new("to_string"),
args: Vec::new(),
},
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"))?;
let owner = ast
.type_(resolved)
.map_err(|_| refuse("a `@default` whose member type is stale"))?;
rust::Expr::Path(Path::new(&[
&owner.name,
&rust::escape_keyword(&enumerator.name),
]))
}
_ => {
let value = ast
.eval_integral(expr)
.map_err(|_| refuse("a `@default` this frontend cannot evaluate"))?;
rust::Expr::Literal(Literal::int(value))
}
})
}
fn call_path(segments: &[&str]) -> rust::Expr {
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(segments))),
args: Vec::new(),
}
}