use std::collections::{HashMap, HashSet, VecDeque};
use crate::ir::RawSpec;
use super::protect::Protection;
use super::types::{EnumGroup, FlatEnum};
pub(super) fn build_flat_enums(
raw: &RawSpec,
req_enums: &HashSet<String>,
is_vulkan: bool,
) -> Vec<FlatEnum> {
let enum_protect = build_ext_enum_protections(raw);
raw.flat_enums
.iter()
.filter(|(name, _)| is_vulkan || req_enums.contains(*name))
.filter_map(|(_, e)| {
let value = e.value.as_deref().or(e.alias.as_deref())?;
let protect = enum_protect
.get(e.name.as_str())
.cloned()
.unwrap_or_default();
Some(FlatEnum {
name: e.name.clone(),
literal_value: value.to_string(),
value: value.to_string(),
comment: e.comment.clone(),
protect,
})
})
.collect()
}
fn build_ext_enum_protections(raw: &RawSpec) -> HashMap<String, Vec<String>> {
let mut tmp: HashMap<&str, Protection> = HashMap::new();
for ext in &raw.extensions {
for require in &ext.requires {
for enum_name in &require.enums {
tmp.entry(enum_name.as_str())
.or_insert_with(Protection::new_guarded)
.add_extension(&ext.protect);
}
}
}
tmp.into_iter()
.map(|(name, prot)| (name.to_string(), prot.into_vec()))
.collect()
}
pub(super) fn build_enum_groups(raw: &RawSpec) -> Vec<EnumGroup> {
raw.enum_groups
.iter()
.map(|g| {
let raw_values: Vec<FlatEnum> = g
.values
.iter()
.filter_map(|v| {
let val = v.value.as_deref().or(v.alias.as_deref())?;
Some(FlatEnum {
name: v.name.clone(),
value: val.to_string(),
literal_value: String::new(), comment: v.comment.clone(),
protect: vec![],
})
})
.collect();
let mut sorted = sort_enum_values(raw_values);
resolve_literal_values(&mut sorted);
EnumGroup {
name: g.name.clone(),
is_bitmask: false,
bitwidth: g.bitwidth.unwrap_or(32),
values: sorted,
}
})
.collect()
}
fn resolve_literal_values(values: &mut [FlatEnum]) {
let mut literals: HashMap<String, String> = HashMap::new();
for v in values.iter_mut() {
let resolved = literals
.get(&v.value)
.cloned()
.unwrap_or_else(|| v.value.clone());
v.literal_value = resolved.clone();
literals.insert(v.name.clone(), resolved);
}
}
fn sort_enum_values(values: Vec<FlatEnum>) -> Vec<FlatEnum> {
let n = values.len();
if n == 0 {
return values;
}
let name_to_idx: HashMap<&str, usize> = values
.iter()
.enumerate()
.map(|(i, v)| (v.name.as_str(), i))
.collect();
let deps: Vec<Option<usize>> = values
.iter()
.map(|v| {
let val = v.value.trim();
let is_numeric = val.starts_with(|c: char| c.is_ascii_digit())
|| val.starts_with("0x")
|| val.starts_with("0X")
|| (val.starts_with('-') && val.len() > 1);
if is_numeric {
return None;
}
name_to_idx.get(val).copied()
})
.collect();
let mut in_degree: Vec<usize> = deps.iter().map(|d| d.is_some() as usize).collect();
let mut rev: Vec<Vec<usize>> = vec![Vec::new(); n];
for (i, dep) in deps.iter().enumerate() {
if let Some(d) = dep {
rev[*d].push(i);
}
}
let mut queue: VecDeque<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
let mut order: Vec<usize> = Vec::with_capacity(n);
while let Some(node) = queue.pop_front() {
order.push(node);
for &dep in &rev[node] {
in_degree[dep] -= 1;
if in_degree[dep] == 0 {
queue.push_back(dep);
}
}
}
for (i, item) in in_degree.iter().enumerate().take(n) {
if *item != 0 {
order.push(i);
}
}
let mut out: Vec<Option<FlatEnum>> = values.into_iter().map(Some).collect();
order.into_iter().map(|i| out[i].take().unwrap()).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_enum(name: &str, value: &str) -> FlatEnum {
FlatEnum {
name: name.to_string(),
value: value.to_string(),
literal_value: String::new(),
comment: String::new(),
protect: vec![],
}
}
#[test]
fn sort_enum_values_numeric_only_preserves_order() {
let input = vec![
make_enum("VK_FOO", "0"),
make_enum("VK_BAR", "1"),
make_enum("VK_BAZ", "2"),
];
let out = sort_enum_values(input);
assert_eq!(out[0].name, "VK_FOO");
assert_eq!(out[1].name, "VK_BAR");
assert_eq!(out[2].name, "VK_BAZ");
}
#[test]
fn sort_enum_values_alias_placed_after_target() {
let input = vec![
make_enum("VK_ALIAS", "VK_ORIGINAL"),
make_enum("VK_ORIGINAL", "42"),
];
let out = sort_enum_values(input);
let original_pos = out.iter().position(|e| e.name == "VK_ORIGINAL").unwrap();
let alias_pos = out.iter().position(|e| e.name == "VK_ALIAS").unwrap();
assert!(original_pos < alias_pos, "alias must come after its target");
}
#[test]
fn sort_enum_values_empty_input() {
assert!(sort_enum_values(vec![]).is_empty());
}
#[test]
fn sort_enum_values_negative_numeric_not_treated_as_alias() {
let input = vec![make_enum("VK_MAX", "-1"), make_enum("VK_ZERO", "0")];
let out = sort_enum_values(input);
assert_eq!(out[0].name, "VK_MAX");
assert_eq!(out[1].name, "VK_ZERO");
}
#[test]
fn sort_enum_values_hex_literal_not_treated_as_alias() {
let input = vec![make_enum("VK_HEX", "0xFF"), make_enum("VK_OTHER", "0x00")];
let out = sort_enum_values(input);
assert_eq!(out[0].name, "VK_HEX");
assert_eq!(out[1].name, "VK_OTHER");
}
}