use std::collections::{HashMap, HashSet, VecDeque};
use anyhow::Result;
use super::{SpecDocs, extract_raw_c};
use crate::ir::RawType;
const MACOS_PTRDIFF_TYPES: &[&str] = &["GLsizeiptr", "GLintptr", "GLsizeiptrARB", "GLintptrARB"];
pub fn parse_types(docs: &SpecDocs<'_, '_>, _spec_name: &str) -> Result<Vec<RawType>> {
let type_nodes = docs.section_children("types");
let mut raw: Vec<RawType> = Vec::with_capacity(type_nodes.len());
for node in &type_nodes {
if node.tag_name().name() != "type" {
continue;
}
let name = if let Some(n) = node.attribute("name") {
n.to_string()
} else if let Some(name_elem) = node
.children()
.find(|n| n.is_element() && n.tag_name().name() == "name")
{
name_elem.text().unwrap_or("").to_string()
} else if let Some(proto) = node
.children()
.find(|n| n.is_element() && n.tag_name().name() == "proto")
{
if let Some(name_elem) = proto
.children()
.find(|n| n.is_element() && n.tag_name().name() == "name")
{
name_elem.text().unwrap_or("").to_string()
} else {
eprintln!("warning: <type> with no discernible name, skipping");
continue;
}
} else {
eprintln!("warning: <type> with no discernible name, skipping");
continue;
};
if name.is_empty() {
continue;
}
let api = node.attribute("api").map(str::to_string);
let category = node.attribute("category").unwrap_or("").to_string();
let requires = node.attribute("requires").map(str::to_string);
let alias = node.attribute("alias").map(str::to_string);
let protect = node.attribute("protect").map(str::to_string);
let bitwidth = node
.attribute("bitwidth")
.and_then(|s| s.parse::<u32>().ok());
let raw_c = if category == "include" {
let text = extract_raw_c(*node).trim().to_string();
if text.is_empty() {
if name.ends_with(".h") && !name.starts_with("vk") {
format!("#include <{}>", name)
} else {
format!("#include \"{}\"", name)
}
} else {
text
}
} else if category == "enum" {
if let Some(ref al) = alias {
format!("typedef enum {} {};", al, name)
} else {
String::new()
}
} else if category == "struct" || category == "union" {
if alias.is_some() {
format!("typedef {} {};", alias.as_deref().unwrap(), name)
} else {
extract_struct_c(*node, &name, &category)
}
} else if category == "funcpointer" {
if node
.children()
.any(|n| n.is_element() && n.tag_name().name() == "proto")
{
extract_funcpointer_c(*node, &name)
} else {
extract_raw_c(*node).trim().to_string()
}
} else {
let mut c = extract_raw_c(*node).trim().to_string();
if c.is_empty()
&& let Some(ref al) = alias
{
c = format!("#define {} {}", name, al);
}
if MACOS_PTRDIFF_TYPES.contains(&name.as_str()) {
c = macos_ptrdiff_guard(&name, &c);
}
c
};
raw.push(RawType {
name,
api,
category,
requires,
alias,
bitwidth,
raw_c,
protect,
});
}
propagate_bitwidth(&mut raw);
for t in raw.iter_mut() {
if t.category == "enum"
&& t.bitwidth == Some(64)
&& let Some(ref al) = t.alias
{
t.raw_c = format!("typedef {} {};", al, t.name);
}
}
let sorted = topological_sort(raw);
Ok(sorted)
}
fn macos_ptrdiff_guard(name: &str, _original: &str) -> String {
format!(
"#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) \\\n\
&& (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)\n\
typedef long {name};\n\
#else\n\
typedef ptrdiff_t {name};\n\
#endif",
name = name
)
}
fn propagate_bitwidth(types: &mut [RawType]) {
let bw64: HashSet<String> = types
.iter()
.filter(|t| t.bitwidth == Some(64))
.map(|t| t.name.clone())
.collect();
for t in types.iter_mut() {
if t.bitwidth.is_none()
&& let Some(ref alias) = t.alias
&& bw64.contains(alias.as_str())
{
t.bitwidth = Some(64);
}
}
}
fn extract_struct_c(node: roxmltree::Node<'_, '_>, name: &str, category: &str) -> String {
let kw = if category == "union" {
"union"
} else {
"struct"
};
let mut members: Vec<String> = Vec::new();
for child in node.children().filter(|n| n.is_element()) {
if child.tag_name().name() != "member" {
continue;
}
if let Some(api) = child.attribute("api") {
if !api.split(',').any(|a| a.trim() == "vulkan") {
continue;
}
}
let text = super::extract_raw_c(child);
let trimmed = text.trim();
if trimmed.is_empty() {
continue;
}
if trimmed.ends_with(';') {
members.push(format!(" {}", trimmed));
} else {
members.push(format!(" {};", trimmed));
}
}
if members.is_empty() {
return format!("typedef {} {} {{}};", kw, name);
}
format!(
"typedef {} {} {{\n{}\n}} {};",
kw,
name,
members.join("\n"),
name
)
}
fn extract_funcpointer_c(node: roxmltree::Node<'_, '_>, name: &str) -> String {
let mut ret = String::new();
if let Some(proto) = node
.children()
.find(|n| n.is_element() && n.tag_name().name() == "proto")
{
for child in proto.children() {
if child.is_text() {
ret.push_str(child.text().unwrap_or(""));
} else if child.is_element() {
match child.tag_name().name() {
"name" => break, "type" => ret.push_str(child.text().unwrap_or("")),
_ => ret.push_str(&super::extract_raw_c(child)),
}
}
}
}
let ret = ret.trim();
let mut params: Vec<String> = Vec::new();
for param in node
.children()
.filter(|n| n.is_element() && n.tag_name().name() == "param")
{
let param_text = super::extract_raw_c(param);
let trimmed = param_text.trim();
if !trimmed.is_empty() {
params.push(trimmed.to_string());
}
}
let params_str = if params.is_empty() {
"void".to_string()
} else {
params.join(", ")
};
format!("typedef {} (VKAPI_PTR *{})({});", ret, name, params_str)
}
pub(crate) fn ident_words(s: &str) -> impl Iterator<Item = &str> {
s.split(|c: char| !c.is_ascii_alphanumeric() && c != '_')
.filter(|w| !w.is_empty())
}
fn topological_sort(types: Vec<RawType>) -> Vec<RawType> {
let mut name_to_indices: HashMap<&str, Vec<usize>> = HashMap::new();
for (i, t) in types.iter().enumerate() {
name_to_indices.entry(&t.name).or_default().push(i);
}
let deps: Vec<Vec<usize>> = types
.iter()
.map(|t| {
let mut d = Vec::new();
if let Some(ref req) = t.requires
&& let Some(idxs) = name_to_indices.get(req.as_str())
{
d.extend_from_slice(idxs);
}
if let Some(ref alias) = t.alias
&& let Some(idxs) = name_to_indices.get(alias.as_str())
{
d.extend_from_slice(idxs);
}
for word in ident_words(&t.raw_c) {
if word == t.name {
continue;
}
if let Some(idxs) = name_to_indices.get(word) {
d.extend_from_slice(idxs);
}
}
d.sort_unstable();
d.dedup();
d
})
.collect();
let mut in_degree: Vec<usize> = deps.iter().map(|d| d.len()).collect();
let mut rev: Vec<Vec<usize>> = vec![Vec::new(); types.len()];
for (i, dep_list) in deps.iter().enumerate() {
for &dep in dep_list {
rev[dep].push(i);
}
}
let mut queue: VecDeque<usize> = (0..types.len()).filter(|&i| in_degree[i] == 0).collect();
let mut order = Vec::with_capacity(types.len());
while let Some(node) = queue.pop_front() {
order.push(node);
for &dependent in &rev[node] {
in_degree[dependent] -= 1;
if in_degree[dependent] == 0 {
queue.push_back(dependent);
}
}
}
if order.len() < types.len() {
for (i, item) in in_degree.iter().enumerate().take(types.len()) {
if *item != 0 {
order.push(i);
}
}
}
let mut types_opt: Vec<Option<RawType>> = types.into_iter().map(Some).collect();
order
.into_iter()
.map(|i| types_opt[i].take().unwrap())
.collect()
}