use crate::ast::{BasicType, CompoundType, Node};
use std::collections::HashSet;
#[derive(Debug)]
pub struct GenericIndex<'a>(pub HashSet<&'a str>);
impl<'a> GenericIndex<'a> {
pub(crate) fn new(ast: &'a Node) -> GenericIndex<'a> {
fn recurse<'a>(v: &'a Node, index: &mut HashSet<&'a str>) -> bool {
let name = match v {
Node::Struct(v) => Some(v.name()),
Node::Union(v) => Some(v.name()),
Node::Typedef(v) => Some(v.alias.unwrap_array().as_str()),
_ => None,
};
if let Some(name) = name {
if index.contains(name) {
return true;
}
}
let contains_opaque = match v {
Node::Type(v) => match v {
BasicType::Opaque => true,
BasicType::Ident(i) => index.contains(i.as_ref()),
_ => false,
},
Node::Struct(v) => v.inner_types().iter().any(|t| match t.unwrap_array() {
BasicType::Opaque => true,
BasicType::Ident(i) => index.contains(i.as_ref()),
_ => false,
}),
Node::Union(v) => v.inner_types().iter().any(|t| match t.unwrap_array() {
BasicType::Opaque => true,
BasicType::Ident(i) => index.contains(i.as_ref()),
_ => false,
}),
Node::Typedef(v) => match v.target {
BasicType::Opaque => true,
_ => index.contains(v.target.as_str()),
},
Node::Root(v) => v.iter().fold(false, |mut acc, v| {
if recurse(v, index) {
acc = true;
}
acc
}),
Node::EOF
| Node::Enum(_)
| Node::Constant(_)
| Node::EnumVariant(_)
| Node::ArrayVariable(_)
| Node::ArrayFixed(_) => false,
Node::Ident(_)
| Node::UnionDefault(_)
| Node::UnionCase(_)
| Node::Option(_)
| Node::UnionDataField(_)
| Node::UnionVoid
| Node::StructDataField(_)
| Node::Array(_) => unreachable!("{:?}", &v),
};
if let Some(name) = name {
if contains_opaque {
index.insert(name);
return true;
}
}
contains_opaque
};
let mut index: HashSet<&'a str> = HashSet::new();
let mut last_size: isize = -1;
while last_size != index.len() as isize {
last_size = index.len() as isize;
recurse(ast, &mut index);
}
GenericIndex(index)
}
pub fn contains(&self, ident: &str) -> bool {
self.0.contains(ident)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{walk, Rule, XDRParser};
use pest::Parser;
#[test]
fn test_generic_pushup_structs() {
let input = r#"
struct stateid4 {
uint32_t seqid;
opaque other[NFS4_OTHER_SIZE];
};
struct generic_field {
stateid4 inner;
};
struct another {
generic_field inner;
};
"#;
let mut ast = XDRParser::parse(Rule::item, input).unwrap();
let ast = walk(ast.next().unwrap()).unwrap();
let got = GenericIndex::new(&ast);
let mut want = HashSet::new();
want.insert("stateid4");
want.insert("generic_field");
want.insert("another");
let mut got: Vec<&str> = got.0.iter().cloned().collect();
let mut want: Vec<&str> = want.iter().cloned().collect();
let got = got.as_mut_slice();
let want = want.as_mut_slice();
got.sort();
want.sort();
assert_eq!(got, want);
}
#[test]
fn test_generic_pushup_structs_reversed() {
let input = r#"
struct another {
generic_field inner;
};
struct generic_field {
stateid4 inner;
};
struct stateid4 {
uint32_t seqid;
opaque other[NFS4_OTHER_SIZE];
};
"#;
let mut ast = XDRParser::parse(Rule::item, input).unwrap();
let ast = walk(ast.next().unwrap()).unwrap();
let got = GenericIndex::new(&ast);
let mut want = HashSet::new();
want.insert("stateid4");
want.insert("generic_field");
want.insert("another");
let mut got: Vec<&str> = got.0.iter().cloned().collect();
let mut want: Vec<&str> = want.iter().cloned().collect();
let got = got.as_mut_slice();
let want = want.as_mut_slice();
got.sort();
want.sort();
assert_eq!(got, want);
}
#[test]
fn test_generic_pushup_typedef() {
let input = r#"
typedef opaque attrlist4<>;
typedef opaque nfs_fh4<NFS4_FHSIZE>;
struct generic_field {
attrlist4 inner;
};
struct another {
nfs_fh4 inner;
};
"#;
let mut ast = XDRParser::parse(Rule::item, input).unwrap();
let ast = walk(ast.next().unwrap()).unwrap();
let got = GenericIndex::new(&ast);
let mut want = HashSet::new();
want.insert("attrlist4");
want.insert("generic_field");
want.insert("nfs_fh4");
want.insert("another");
let mut got: Vec<&str> = got.0.iter().cloned().collect();
let mut want: Vec<&str> = want.iter().cloned().collect();
let got = got.as_mut_slice();
let want = want.as_mut_slice();
got.sort();
want.sort();
assert_eq!(got, want);
}
#[test]
fn test_generic_pushup_union() {
let input = r#"
struct READ4resok {
bool eof;
opaque data<>;
};
union READ4res switch (nfsstat4 status) {
case NFS4_OK:
READ4resok resok4;
default:
void;
};
"#;
let mut ast = XDRParser::parse(Rule::item, input).unwrap();
let ast = walk(ast.next().unwrap()).unwrap();
let got = GenericIndex::new(&ast);
let mut want = HashSet::new();
want.insert("READ4resok");
want.insert("READ4res");
let mut got: Vec<&str> = got.0.iter().cloned().collect();
let mut want: Vec<&str> = want.iter().cloned().collect();
let got = got.as_mut_slice();
let want = want.as_mut_slice();
got.sort();
want.sort();
assert_eq!(got, want);
}
}