Skip to content

Commit

Permalink
analysis/cdecl: add a way to dump a "pseudo-Rust" view of all parsed …
Browse files Browse the repository at this point in the history
…`CDecl`s.
  • Loading branch information
eddyb committed May 25, 2024
1 parent 4d4cc5d commit 58005bd
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 20 deletions.
88 changes: 88 additions & 0 deletions analysis/src/cdecl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,92 @@ impl<'a> CDecl<'a> {
bitfield_width,
})
}

pub fn to_pseudo_rust(&self) -> String {
self.to_pseudo_rust_with_external_lengths(&[])
}
pub fn to_pseudo_rust_with_external_lengths(&self, external_lengths: &[&str]) -> String {
let CDecl {
ty,
name,
bitfield_width,
} = self;
let decl = format!(
"{name}: {}",
ty.to_pseudo_rust_with_external_lengths(external_lengths)
);
match bitfield_width {
Some(width) => format!("#[bitfield({width})] {decl}"),
None => decl,
}
}
}

impl CType<'_> {
pub fn to_pseudo_rust_with_external_lengths(&self, external_lengths: &[&str]) -> String {
if let Some((len, remaining_lengths)) = external_lengths.split_first() {
match self {
CType::Ptr {
implicit_for_decay: false,
is_const,
pointee,
} => {
let const_or_mut = if *is_const { "const" } else { "mut" };
format!(
"*{const_or_mut} [{}; dyn {len}]",
pointee.to_pseudo_rust_with_external_lengths(remaining_lengths)
)
}
_ => unreachable!(),
}
} else {
self.to_pseudo_rust()
}
}
pub fn to_pseudo_rust(&self) -> String {
match self {
&CType::Base(CBaseType { struct_tag, name }) => {
if struct_tag {
format!("/*struct*/{name}")
} else {
name.to_string()
}
}
CType::Ptr {
implicit_for_decay,
is_const,
pointee,
} => {
if let CType::Func { ret_ty, params } = &**pointee {
assert!(!implicit_for_decay);
assert!(!is_const);
let params = if params.is_empty() {
"".to_string()
} else {
params.iter().fold("\n".to_string(), |params, param| {
params + " " + &param.to_pseudo_rust() + ",\n"
})
};
format!(
"unsafe extern fn({params}){}",
ret_ty
.as_ref()
.map(|ty| format!(" -> {}", ty.to_pseudo_rust()))
.unwrap_or_default()
)
} else {
let const_or_mut = if *is_const { "const" } else { "mut" };
format!("*{const_or_mut} {}", pointee.to_pseudo_rust())
}
}
CType::Array { element, len } => {
let len = match len {
CArrayLen::Named(name) => name.to_string(),
CArrayLen::Literal(len) => len.to_string(),
};
format!("[{}; {len}]", element.to_pseudo_rust())
}
CType::Func { .. } => unreachable!(),
}
}
}
44 changes: 44 additions & 0 deletions analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,50 @@ impl Analysis {
video: Library::new(vulkan_headers_path.join("registry/video.xml")),
}
}

pub fn dump_as_pseudo_rust(&self) {
for fp in &self.vk._xml.funcpointers {
eprintln!(
"type {} = {};",
fp.c_decl.name,
fp.c_decl.ty.to_pseudo_rust()
);
}
for st in &self.vk._xml.structs {
eprintln!("struct {} {{", st.name);
for m in &st.members {
if m.len.len() > 1 {}
let len = if !m.altlen.is_empty() {
&m.altlen
} else {
&m.len
};
eprint!(" {}", m.c_decl.to_pseudo_rust_with_external_lengths(len));
if let Some(val) = &m.values {
eprint!(" = {val}");
}
eprintln!(",");
}
eprintln!("}}");
}
for cmd in &self.vk._xml.commands {
eprintln!("unsafe extern fn {}(", cmd.name);
for p in &cmd.params {
let len = if !p.altlen.is_empty() {
&p.altlen
} else {
&p.len
};
eprint!(" {}", p.c_decl.to_pseudo_rust_with_external_lengths(len));
eprintln!(",");
}
eprint!(")");
if let Some(ret_ty) = &cmd.return_type {
eprint!(" -> {}", ret_ty.to_pseudo_rust());
}
eprintln!(";");
}
}
}

#[derive(Debug)]
Expand Down
48 changes: 28 additions & 20 deletions analysis/src/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,49 +15,50 @@ pub trait UnwrapBorrowed<'a, B>
where
B: ToOwned + ?Sized,
{
fn unwrap_borrowed(&self) -> &'a B;
fn unwrap_borrowed_or_leak_owned(self) -> &'a B;
}

impl<'a, B> UnwrapBorrowed<'a, B> for Cow<'a, B>
where
B: ToOwned + ?Sized,
B: ToOwned + ?Sized + std::fmt::Debug,
{
fn unwrap_borrowed(&self) -> &'a B {
fn unwrap_borrowed_or_leak_owned(self) -> &'a B {
match self {
Cow::Borrowed(b) => b,
Cow::Owned(_) => panic!("Called `unwrap_borrowed` on `Owned` value"),
Cow::Owned(o) => {
let leaked = std::borrow::Borrow::borrow(Box::leak(Box::new(o)));
debug!("unwrap_borrowed_or_leak_owned: leaking `Owned({leaked:?})`");
leaked
}
}
}
}

/// Converts `roxmltree`'s `StringStorage` to a `XmlStr`
fn make_xml_str(string_storage: StringStorage<'static>) -> XmlStr {
fn make_xml_str(string_storage: &StringStorage<'static>) -> XmlStr {
match string_storage {
StringStorage::Borrowed(s) => Cow::Borrowed(s),
StringStorage::Owned(s) => Cow::Owned((*s).into()),
StringStorage::Owned(s) => Cow::Owned((**s).into()),
}
}

/// Retrieves the value of the `node`'s attribute named `name`.
fn attribute(node: Node, name: &str) -> Option<XmlStr> {
node.attribute_node(name)
.map(|attr| make_xml_str(attr.value_storage().clone()))
.map(|attr| make_xml_str(attr.value_storage()))
}

/// Retrieves the ','-separated values of the `node`'s attribute named `name`.
fn attribute_comma_separated(node: Node, name: &str) -> Vec<&'static str> {
attribute(node, name)
.map(|value| value.unwrap_borrowed().split(',').collect())
.map(|value| value.unwrap_borrowed_or_leak_owned().split(',').collect())
.unwrap_or_default()
}

/// Retrieves the text inside the next child element of `node` named `name`.
fn child_text(node: Node, name: &str) -> Option<XmlStr> {
let child = node.children().find(|node| node.has_tag_name(name));
child.map(|node| match node.text_storage().unwrap().clone() {
StringStorage::Borrowed(s) => Cow::Borrowed(s),
StringStorage::Owned(s) => Cow::Owned((*s).into()),
})
child.map(|node| make_xml_str(node.text_storage().unwrap()))
}

/// Returns [`true`] when the `node`'s "api" attribute matches the `expected` API.
Expand All @@ -81,7 +82,8 @@ impl CDecl<'static> {
fn from_xml(mode: CDeclMode, children: roxmltree::Children<'_, 'static>) -> CDecl<'static> {
let mut c_tokens = vec![];
for child in children {
let text = || make_xml_str(child.text_storage().unwrap().clone()).unwrap_borrowed();
let text =
|| make_xml_str(child.text_storage().unwrap()).unwrap_borrowed_or_leak_owned();
match child.node_type() {
NodeType::Text => {
CTok::lex_into(text(), &mut c_tokens).unwrap();
Expand Down Expand Up @@ -424,7 +426,7 @@ pub struct StructureMember {
pub c_decl: CDecl<'static>,
pub values: Option<XmlStr>,
pub len: Vec<&'static str>,
pub altlen: Option<XmlStr>,
pub altlen: Vec<&'static str>,
pub optional: Vec<&'static str>,
}

Expand All @@ -434,7 +436,7 @@ impl StructureMember {
c_decl: CDecl::from_xml(CDeclMode::StructMember, node.children()),
values: attribute(node, "values"),
len: attribute_comma_separated(node, "len"),
altlen: attribute(node, "altlen"),
altlen: attribute_comma_separated(node, "altlen"),
optional: attribute_comma_separated(node, "optional"),
}
}
Expand Down Expand Up @@ -581,17 +583,17 @@ impl BitMask {
#[derive(Debug)]
pub struct CommandParam {
pub c_decl: CDecl<'static>,
pub len: Option<XmlStr>,
pub altlen: Option<XmlStr>,
pub len: Vec<&'static str>,
pub altlen: Vec<&'static str>,
pub optional: Vec<&'static str>,
}

impl CommandParam {
fn from_node(node: Node) -> CommandParam {
CommandParam {
c_decl: CDecl::from_xml(CDeclMode::FuncParam, node.children()),
len: attribute(node, "len"),
altlen: attribute(node, "altlen"),
len: attribute_comma_separated(node, "len"),
altlen: attribute_comma_separated(node, "altlen"),
optional: attribute_comma_separated(node, "optional"),
}
}
Expand Down Expand Up @@ -747,7 +749,13 @@ impl Require {
fn from_node(node: Node, api: &str) -> Require {
let mut value = Require {
depends: attribute(node, "depends")
.map(|value| (value.unwrap_borrowed().split(',').map(Depends::from_str)).collect())
.map(|value| {
(value
.unwrap_borrowed_or_leak_owned()
.split(',')
.map(Depends::from_str))
.collect()
})
.unwrap_or_default(),
..Default::default()
};
Expand Down
3 changes: 3 additions & 0 deletions generator-rewrite/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ fn main() {
tracing_subscriber::fmt::init();
let _analysis = Analysis::new("generator/Vulkan-Headers");
// dbg!(_analysis);
if true {
_analysis.dump_as_pseudo_rust();
}
}

0 comments on commit 58005bd

Please sign in to comment.