use zerodds_ast::{Ast, CaseLabel, MemberNode, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Literal, Path};
use zerodds_lang_cst::rust;
pub(crate) const NO_MEMBER: &str = "_NoMember";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DiscriminatorKind {
Boolean,
Character,
Enumeration(Id<TypeNode>),
Integer,
}
#[derive(Debug)]
pub(crate) struct RustCase<'a> {
pub(crate) member: &'a MemberNode,
pub(crate) variant: String,
pub(crate) labels: Vec<i128>,
pub(crate) is_default: bool,
}
impl RustCase<'_> {
pub(crate) fn carries_discriminator(&self) -> bool {
self.is_default || self.labels.len() > 1
}
}
#[derive(Debug)]
pub(crate) struct RustUnion<'a> {
pub(crate) discriminator: Id<TypeNode>,
pub(crate) kind: DiscriminatorKind,
pub(crate) cases: Vec<RustCase<'a>>,
pub(crate) no_member: bool,
}
impl RustUnion<'_> {
pub(crate) fn all_labels(&self) -> Vec<i128> {
self.cases
.iter()
.flat_map(|case| case.labels.iter().copied())
.collect()
}
}
pub(crate) fn shape(ast: &Ast, union_type: Id<TypeNode>) -> Result<RustUnion<'_>, &'static str> {
let node = ast
.type_(union_type)
.map_err(|_| "a union the model does not hold")?;
let TypeKind::Union {
discriminator,
cases,
default_case,
} = &node.kind
else {
return Err("not a union");
};
let discriminator = unalias(
ast,
discriminator
.resolved()
.ok_or("a union with an unresolved discriminator")?,
)?;
let kind = match ast.type_(discriminator).map(|t| &t.kind) {
Ok(TypeKind::Primitive(PrimitiveKind::Boolean)) => DiscriminatorKind::Boolean,
Ok(TypeKind::Primitive(PrimitiveKind::Char | PrimitiveKind::WChar)) => {
DiscriminatorKind::Character
}
Ok(TypeKind::Enumeration { .. }) => DiscriminatorKind::Enumeration(discriminator),
Ok(TypeKind::Primitive(_)) => DiscriminatorKind::Integer,
_ => {
return Err(
"a union whose discriminator is no integer, character, boolean or enumeration",
);
}
};
let mut rust_cases = Vec::new();
for case in cases {
let case_node = ast
.case(*case)
.map_err(|_| "a union case the model does not hold")?;
let member = ast
.member(case_node.member)
.map_err(|_| "a union case without its member")?;
let mut labels = Vec::new();
let mut is_default = *default_case == Some(*case);
for label in &case_node.labels {
match label {
CaseLabel::Default => is_default = true,
CaseLabel::Value(expr) => labels.push(
ast.eval_integral(*expr)
.map_err(|_| "a union case whose label does not evaluate")?,
),
}
}
if !is_default && labels.is_empty() {
return Err("a union case carrying no case label");
}
rust_cases.push(RustCase {
member,
variant: crate::adapter::cst::variant_name(&member.name),
labels,
is_default,
});
}
let has_default = rust_cases.iter().any(|case| case.is_default);
let mut distinct: Vec<i128> = rust_cases
.iter()
.flat_map(|case| case.labels.iter().copied())
.collect();
distinct.sort_unstable();
distinct.dedup();
let covered = domain_size(ast, discriminator, kind)
.is_some_and(|size| u128::try_from(distinct.len()).is_ok_and(|n| n >= size));
Ok(RustUnion {
discriminator,
kind,
cases: rust_cases,
no_member: !has_default && !covered,
})
}
fn domain_size(ast: &Ast, discriminator: Id<TypeNode>, kind: DiscriminatorKind) -> Option<u128> {
match kind {
DiscriminatorKind::Boolean => Some(2),
DiscriminatorKind::Enumeration(enumeration) => {
u128::try_from(ast.enumerators_of(enumeration).len()).ok()
}
DiscriminatorKind::Character | DiscriminatorKind::Integer => {
match ast.type_(discriminator).map(|t| &t.kind) {
Ok(TypeKind::Primitive(
PrimitiveKind::Char
| PrimitiveKind::Octet
| PrimitiveKind::Int8
| PrimitiveKind::UInt8,
)) => Some(256),
Ok(TypeKind::Primitive(
PrimitiveKind::WChar | PrimitiveKind::Int16 | PrimitiveKind::UInt16,
)) => Some(65_536),
_ => None,
}
}
}
}
fn unalias(ast: &Ast, mut type_: Id<TypeNode>) -> Result<Id<TypeNode>, &'static str> {
for _ in 0..64 {
match ast.type_(type_).map(|t| &t.kind) {
Ok(TypeKind::Alias { aliased }) => {
type_ = aliased
.resolved()
.ok_or("a discriminator alias that does not resolve")?;
}
Ok(_) => return Ok(type_),
Err(_) => return Err("a discriminator the model does not hold"),
}
}
Err("a discriminator alias chain longer than 64")
}
pub(crate) fn label_literal(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
kind: DiscriminatorKind,
value: i128,
) -> Result<LabelSource, &'static str> {
Ok(match kind {
DiscriminatorKind::Integer => LabelSource::Literal(Literal::int(value)),
DiscriminatorKind::Boolean => LabelSource::Literal(Literal::Bool(value != 0)),
DiscriminatorKind::Character => LabelSource::Literal(Literal::Char(
u32::try_from(value)
.ok()
.and_then(char::from_u32)
.ok_or("a character label outside Unicode")?,
)),
DiscriminatorKind::Enumeration(enumeration) => {
LabelSource::Path(enumerator_path(ast, from, enumeration, value)?)
}
})
}
#[derive(Debug, Clone)]
pub(crate) enum LabelSource {
Literal(Literal),
Path(Path),
}
impl LabelSource {
pub(crate) fn expr(&self) -> rust::Expr {
match self {
Self::Literal(literal) => rust::Expr::Literal(literal.clone()),
Self::Path(path) => rust::Expr::Path(path.clone()),
}
}
pub(crate) fn pattern(&self) -> rust::Pattern {
match self {
Self::Literal(literal) => rust::Pattern::Literal(literal.clone()),
Self::Path(path) => rust::Pattern::Path(path.clone()),
}
}
}
pub(crate) fn enumerator_path(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
enumeration: Id<TypeNode>,
value: i128,
) -> Result<Path, &'static str> {
let enumerator = ast
.enumerators_of(enumeration)
.iter()
.copied()
.find(|e| ast.effective_enumerator_value(*e) == Some(value))
.ok_or("an enumeration label with no enumerator of its value")?;
enumerator_path_of(ast, from, enumeration, enumerator)
}
pub(crate) fn enumerator_path_of(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
enumeration: Id<TypeNode>,
enumerator: Id<zerodds_ast::EnumeratorNode>,
) -> Result<Path, &'static str> {
let node = ast
.type_(enumeration)
.map_err(|_| "an enumeration the model does not hold")?;
let enumerator = ast
.enumerator(enumerator)
.map_err(|_| "an enumerator the model does not hold")?;
let path = crate::adapter::cst::relative_path(ast, from, node)
.map_err(|_| "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(zerodds_lang_cst::rust::escape_keyword(&enumerator.name));
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
Ok(Path::new(&borrowed))
}