use std::io::Write;
use syn;
use bindgen::config::{Config, Language};
use bindgen::ir::{AnnotationSet, Cfg, CfgWrite, Documentation, GenericParams, GenericPath, Item,
ItemContainer, Repr, Struct, Type};
use bindgen::rename::{IdentifierType, RenameRule};
use bindgen::utilities::find_first_some;
use bindgen::writer::{Source, SourceWriter};
#[derive(Debug, Clone)]
pub struct EnumVariant {
pub name: String,
pub discriminant: Option<u64>,
pub body: Option<(String, Struct)>,
pub documentation: Documentation,
}
impl EnumVariant {
pub fn load(
is_tagged: bool,
variant: &syn::Variant,
mod_cfg: &Option<Cfg>,
) -> Result<Self, String> {
let discriminant = match variant.discriminant {
Some(syn::ConstExpr::Lit(syn::Lit::Int(i, _))) => Some(i),
Some(_) => {
return Err("Unsupported discriminant.".to_owned());
}
None => None,
};
let body = match variant.data {
syn::VariantData::Unit => None,
syn::VariantData::Struct(ref fields) | syn::VariantData::Tuple(ref fields) => {
Some(Struct {
name: format!("{}_Body", variant.ident),
generic_params: GenericParams::default(),
fields: {
let mut res = Vec::new();
if is_tagged {
res.push((
"tag".to_string(),
Type::Path(GenericPath {
name: "Tag".to_string(),
generics: vec![],
}),
Documentation::none(),
));
}
for (i, field) in fields.iter().enumerate() {
if let Some(ty) = Type::load(&field.ty)? {
res.push((
match field.ident {
Some(ref ident) => ident.to_string(),
None => i.to_string(),
},
ty,
Documentation::load(&field.attrs),
));
}
}
res
},
is_tagged,
tuple_struct: match variant.data {
syn::VariantData::Tuple(_) => true,
_ => false,
},
cfg: Cfg::append(mod_cfg, Cfg::load(&variant.attrs)),
annotations: AnnotationSet::load(&variant.attrs)?,
documentation: Documentation::none(),
})
}
};
Ok(EnumVariant {
name: variant.ident.to_string(),
discriminant,
body: body.map(|body| {
(
RenameRule::SnakeCase
.apply_to_pascal_case(variant.ident.as_ref(), IdentifierType::StructMember),
body,
)
}),
documentation: Documentation::load(&variant.attrs),
})
}
}
impl Source for EnumVariant {
fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
self.documentation.write(config, out);
write!(out, "{}", self.name);
if let Some(discriminant) = self.discriminant {
write!(out, " = {}", discriminant);
}
out.write(",");
}
}
#[derive(Debug, Clone)]
pub struct Enum {
pub name: String,
pub repr: Repr,
pub variants: Vec<EnumVariant>,
pub tag: Option<String>,
pub cfg: Option<Cfg>,
pub annotations: AnnotationSet,
pub documentation: Documentation,
}
impl Enum {
pub fn load(
name: String,
values: &Vec<syn::Variant>,
attrs: &Vec<syn::Attribute>,
mod_cfg: &Option<Cfg>,
) -> Result<Enum, String> {
let repr = Repr::load(attrs);
if repr != Repr::C && repr != Repr::USize && repr != Repr::U32 && repr != Repr::U16
&& repr != Repr::U8 && repr != Repr::ISize && repr != Repr::I32
&& repr != Repr::I16 && repr != Repr::I8
{
return Err("Enum not marked with a valid repr(prim) or repr(C).".to_owned());
}
let mut variants = Vec::new();
let mut is_tagged = false;
for variant in values {
let variant = EnumVariant::load(repr != Repr::C, variant, mod_cfg)?;
is_tagged = is_tagged || variant.body.is_some();
variants.push(variant);
}
let annotations = AnnotationSet::load(attrs)?;
if let Some(names) = annotations.list("enum-trailing-values") {
for name in names {
variants.push(EnumVariant {
name,
discriminant: None,
body: None,
documentation: Documentation::none(),
});
}
}
Ok(Enum {
name,
repr,
variants,
tag: if is_tagged {
Some("Tag".to_string())
} else {
None
},
cfg: Cfg::append(mod_cfg, Cfg::load(attrs)),
annotations,
documentation: Documentation::load(attrs),
})
}
}
impl Item for Enum {
fn name(&self) -> &str {
&self.name
}
fn cfg(&self) -> &Option<Cfg> {
&self.cfg
}
fn annotations(&self) -> &AnnotationSet {
&self.annotations
}
fn annotations_mut(&mut self) -> &mut AnnotationSet {
&mut self.annotations
}
fn container(&self) -> ItemContainer {
ItemContainer::Enum(self.clone())
}
fn rename_for_config(&mut self, config: &Config) {
if config.language == Language::C && self.tag.is_some() {
let new_tag = format!("{}_Tag", self.name);
if self.repr != Repr::C {
for variant in &mut self.variants {
if let Some((_, ref mut body)) = variant.body {
body.fields[0].1 = Type::Path(GenericPath {
name: new_tag.clone(),
generics: vec![],
});
}
}
}
self.tag = Some(new_tag);
}
for variant in &mut self.variants {
if let Some((_, ref mut body)) = variant.body {
body.rename_for_config(config);
}
}
if config.enumeration.prefix_with_name
|| self.annotations.bool("prefix-with-name").unwrap_or(false)
{
for variant in &mut self.variants {
variant.name = format!("{}_{}", self.name, variant.name);
if let Some((_, ref mut body)) = variant.body {
body.name = format!("{}_{}", self.name, body.name);
}
}
}
let rules = [
self.annotations.parse_atom::<RenameRule>("rename-all"),
config.enumeration.rename_variants,
];
if let Some(r) = find_first_some(&rules) {
self.variants = self.variants
.iter()
.map(|variant| {
EnumVariant {
name: r.apply_to_pascal_case(
&variant.name,
IdentifierType::EnumVariant(self),
),
discriminant: variant.discriminant.clone(),
body: variant.body.as_ref().map(|body| {
(
r.apply_to_snake_case(&body.0, IdentifierType::StructMember),
body.1.clone(),
)
}),
documentation: variant.documentation.clone(),
}
})
.collect();
}
}
}
impl Source for Enum {
fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
let size = match self.repr {
Repr::C => None,
Repr::USize => Some("uintptr_t"),
Repr::U32 => Some("uint32_t"),
Repr::U16 => Some("uint16_t"),
Repr::U8 => Some("uint8_t"),
Repr::ISize => Some("intptr_t"),
Repr::I32 => Some("int32_t"),
Repr::I16 => Some("int16_t"),
Repr::I8 => Some("int8_t"),
_ => unreachable!(),
};
self.cfg.write_before(config, out);
self.documentation.write(config, out);
let is_tagged = self.tag.is_some();
if is_tagged && config.language == Language::Cxx {
out.write(if size.is_some() { "union " } else { "struct " });
write!(out, "{}", self.name);
out.open_brace();
}
let enum_name = if let Some(ref tag) = self.tag {
tag
} else {
&self.name
};
if config.language == Language::C {
if size.is_none() {
out.write("typedef enum");
} else {
write!(out, "enum {}", enum_name);
}
} else {
if let Some(prim) = size {
write!(out, "enum class {} : {}", enum_name, prim);
} else {
write!(out, "enum class {}", enum_name);
}
}
out.open_brace();
for (i, variant) in self.variants.iter().enumerate() {
if i != 0 {
out.new_line()
}
variant.write(config, out);
}
if config.enumeration.add_sentinel(&self.annotations) {
out.new_line();
out.new_line();
out.write("Sentinel /* this must be last for serialization purposes. */");
}
if config.language == Language::C && size.is_none() {
out.close_brace(false);
write!(out, " {};", enum_name);
} else {
out.close_brace(true);
}
if config.language == Language::C {
if let Some(prim) = size {
out.new_line();
write!(out, "typedef {} {};", prim, enum_name);
}
}
if is_tagged {
for variant in &self.variants {
if let Some((_, ref body)) = variant.body {
out.new_line();
out.new_line();
body.write(config, out);
}
}
out.new_line();
out.new_line();
if config.language == Language::C {
out.write("typedef union");
out.open_brace();
}
write!(out, "{} tag;", enum_name);
out.new_line();
if size.is_none() {
out.write("union");
out.open_brace();
}
for (i, &(ref field_name, ref body)) in self.variants
.iter()
.filter_map(|variant| variant.body.as_ref())
.enumerate()
{
if i != 0 {
out.new_line();
}
write!(out, "{} {};", body.name, field_name);
}
if size.is_none() {
out.close_brace(true);
}
if config.language == Language::C {
out.close_brace(false);
write!(out, " {};", self.name);
} else {
out.close_brace(true);
}
}
self.cfg.write_after(config, out);
}
}