use crate::BigUint;
use crate::HashSet;
use crate::analyzer_error::{AnalyzerError, UnevaluableValueKind};
use crate::attribute::EnumEncodingItem;
use crate::conv::utils::TypePosition;
use crate::conv::{self, Context, Conv};
use crate::ir::{self, IrResult};
use crate::symbol::{EnumMemberValue, EnumProperty, Symbol, SymbolKind};
use crate::symbol_table;
use num_traits::ToPrimitive;
pub fn resolve_enum(list: &[Symbol]) -> Vec<AnalyzerError> {
let mut errors = Vec::new();
let mut context = conv::Context::default();
for symbol in list {
let SymbolKind::Enum(r#enum) = &symbol.kind else {
unreachable!();
};
let enum_width = eval_enum_width(&mut context, symbol, r#enum, &mut errors);
let mut pre_value = None;
let mut member_width = 0;
let mut seen_values: HashSet<BigUint> = HashSet::default();
let mut seen_xz: HashSet<(BigUint, BigUint)> = HashSet::default();
for id in &r#enum.members {
let mut symbol = symbol_table::get(*id).unwrap();
let value = eval_enum_member_value(
&mut context,
&symbol,
r#enum,
pre_value.as_ref(),
enum_width,
&mut member_width,
&mut seen_xz,
&mut errors,
);
if matches!(value, EnumMemberValue::UnevaluableValue) {
errors.push(AnalyzerError::unevaluable_value(
UnevaluableValueKind::EnumVariant,
&symbol.token.into(),
));
}
if let Some(value) = value.value() {
let width = value.bits() as usize;
if enum_width > 0 && width > enum_width {
errors.push(AnalyzerError::too_large_enum_variant(
&symbol.token.to_string(),
value.to_isize().unwrap_or(isize::MAX),
enum_width,
&symbol.token.into(),
));
}
member_width = member_width.max(width);
if !seen_values.insert(value.clone()) {
errors.push(AnalyzerError::duplicate_enum_variant(
&symbol.token.to_string(),
&symbol.token.into(),
));
}
}
if let SymbolKind::EnumMember(mut member) = symbol.kind {
member.value = value.clone();
symbol.kind = SymbolKind::EnumMember(member);
symbol_table::update(symbol);
}
pre_value = Some(value);
}
let mut symbol = symbol.clone();
if let SymbolKind::Enum(mut r#enum) = symbol.kind {
r#enum.width = 1.max(enum_width.max(member_width));
symbol.kind = SymbolKind::Enum(r#enum);
symbol_table::update(symbol);
}
}
errors.append(&mut context.drain_errors());
errors
}
fn eval_enum_width(
context: &mut Context,
symbol: &Symbol,
r#enum: &EnumProperty,
errors: &mut Vec<AnalyzerError>,
) -> usize {
let Some(r#type) = &r#enum.r#type else {
return 0;
};
let width = if let Ok(x) = r#type.to_ir_type(context, TypePosition::Enum) {
x.total_width().unwrap_or(0)
} else {
0
};
if width > 0 && calc_width(r#enum.members.len() - 1) > width {
errors.push(AnalyzerError::too_much_enum_variant(
&symbol.token.to_string(),
r#enum.members.len(),
width,
&symbol.token.into(),
));
}
width
}
#[allow(clippy::too_many_arguments)]
fn eval_enum_member_value(
context: &mut Context,
symbol: &Symbol,
r#enum: &EnumProperty,
pre_value: Option<&EnumMemberValue>,
enum_width: usize,
member_width: &mut usize,
seen_xz: &mut HashSet<(BigUint, BigUint)>,
errors: &mut Vec<AnalyzerError>,
) -> EnumMemberValue {
let SymbolKind::EnumMember(enum_member) = &symbol.kind else {
unreachable!();
};
if let EnumMemberValue::ExplicitValue(ref expression, _) = enum_member.value {
let Ok(mut expr): IrResult<ir::Expression> = Conv::conv(context, expression) else {
return EnumMemberValue::UnevaluableValue;
};
let comptime = expr.eval_comptime(context, None);
let Ok(value) = comptime.get_value() else {
return EnumMemberValue::UnevaluableValue;
};
if value.is_xz() {
if matches!(
&r#enum.encoding,
EnumEncodingItem::OneHot | EnumEncodingItem::Gray
) {
EnumMemberValue::UnevaluableValue
} else {
if let Some(r#type) = &r#enum.r#type
&& r#type.kind.is_2state()
{
let src = comptime.r#type.to_string();
let dst = r#type.to_string();
errors.push(AnalyzerError::mismatch_assignment(
&src,
&dst,
&expression.into(),
&[],
));
}
let needed = value.payload().bits().max(value.mask_xz().bits()) as usize;
if enum_width > 0 && needed > enum_width {
errors.push(AnalyzerError::too_large_enum_variant(
&symbol.token.to_string(),
value.payload().to_isize().unwrap_or(isize::MAX),
enum_width,
&symbol.token.into(),
));
}
*member_width = (*member_width).max(needed);
let key = (value.payload().into_owned(), value.mask_xz().into_owned());
if !seen_xz.insert(key) {
errors.push(AnalyzerError::duplicate_enum_variant(
&symbol.token.to_string(),
&symbol.token.into(),
));
}
enum_member.value.clone()
}
} else {
let value = value.payload().into_owned();
let is_valid = match r#enum.encoding {
EnumEncodingItem::OneHot => value.count_ones() == 1,
EnumEncodingItem::Gray => get_enum_member_next_value(r#enum.encoding, pre_value)
.map(|x| value == x)
.unwrap_or(true),
_ => true,
};
if is_valid {
EnumMemberValue::ExplicitValue(expression.clone(), Some(value))
} else {
errors.push(AnalyzerError::invalid_enum_variant(
&symbol.token.to_string(),
&r#enum.encoding.to_string(),
&symbol.token.into(),
));
enum_member.value.clone()
}
}
} else if let Some(value) = get_enum_member_next_value(r#enum.encoding, pre_value) {
EnumMemberValue::ImplicitValue(value)
} else {
EnumMemberValue::UnevaluableValue
}
}
fn calc_width(value: usize) -> usize {
(usize::BITS - value.leading_zeros()) as usize
}
fn get_enum_member_next_value(
encoding: EnumEncodingItem,
pre_value: Option<&EnumMemberValue>,
) -> Option<BigUint> {
if let Some(value) = pre_value
&& let Some(value) = value.value()
{
match encoding {
EnumEncodingItem::Sequential => Some(value + 1u32),
EnumEncodingItem::OneHot => Some(value << 1u32),
EnumEncodingItem::Gray => {
let mut n = value.clone();
let mut g = value >> 1u32;
while g != BigUint::from(0u32) {
n ^= &g;
g >>= 1u32;
}
let m = n + 1u32;
let half = &m >> 1u32;
Some(&m ^ &half)
}
}
} else if pre_value.is_none() {
if matches!(encoding, EnumEncodingItem::OneHot) {
Some(BigUint::from(1u32))
} else {
Some(BigUint::from(0u32))
}
} else {
None
}
}