use syntax::ast;
use syntax::print;
use types;
use Error;
use Level;
fn check_pub_use(item: &ast::Item, expected: &ast::Path) -> bool {
if let ast::ItemKind::Use(ref path) = item.node {
if let ast::Visibility::Public = item.vis {
if let ast::ViewPath_::ViewPathGlob(ref path) = path.node {
let mut segments = path.segments.iter();
if path.is_global() {
segments.next();
}
return segments.eq(expected.segments.iter());
}
}
}
false
}
pub fn parse_crate(krate: &ast::Crate, path: &ast::Path) -> Result<String, Vec<Error>> {
if !krate.module.items.iter().any(|item| check_pub_use(&item, &path)) {
return Err(vec![
Error {
level: Level::Error,
span: None,
message: format!("module `{}` has not been brought into global scope", path),
},
Error {
level: Level::Help,
span: None,
message: format!("try putting `pub use {}::*` in your root source file", path),
},
]);
}
let mut current_module = &krate.module;
for module in &path.segments {
let mut found = false;
for item in ¤t_module.items {
if let ast::ItemKind::Mod(ref new_module) = item.node {
if module.identifier == item.ident {
current_module = new_module;
found = true;
break;
}
}
}
if !found {
return Err(vec![Error {
level: Level::Fatal,
span: None,
message: format!("module `{}` could not be found", module.identifier),
}]);
}
}
parse_mod(¤t_module)
}
pub fn parse_mod(module: &ast::Mod) -> Result<String, Vec<Error>> {
let mut buffer = String::new();
let mut errors = vec![];
for item in &module.items {
if let ast::Visibility::Inherited = item.vis { continue; }
let res = match item.node {
ast::ItemKind::Ty(..) => parse_ty(item),
ast::ItemKind::Enum(..) => parse_enum(item),
ast::ItemKind::Struct(..) => parse_struct(item),
ast::ItemKind::Fn(..) => parse_fn(item),
_ => Ok(None),
};
match res {
Err(error) => errors.push(error),
Ok(Some(buf)) => buffer.push_str(&buf),
Ok(None) => {}, };
}
if errors.is_empty() {
Ok(buffer)
} else {
Err(errors)
}
}
fn parse_ty(item: &ast::Item) -> Result<Option<String>, Error> {
let (_, docs) = parse_attr(&item.attrs, |_| true, |attr| retrieve_docstring(attr, ""));
let mut buffer = String::new();
buffer.push_str(&docs);
let name = item.ident.name.as_str();
let new_type = match item.node {
ast::ItemKind::Ty(ref ty, ref generics) => {
if generics.is_parameterized() { return Ok(None); }
try_some!(types::rust_to_c(&*ty, &name))
},
_ => {
return Err(Error {
level: Level::Bug,
span: Some(item.span),
message: "`parse_ty` called on wrong `Item_`".into(),
});
},
};
buffer.push_str(&format!("typedef {};\n\n", new_type));
Ok(Some(buffer))
}
fn parse_enum(item: &ast::Item) -> Result<Option<String>, Error> {
let (repr_c, docs) = parse_attr(&item.attrs, check_repr_c, |attr| retrieve_docstring(attr, ""));
if !repr_c { return Ok(None); }
let mut buffer = String::new();
buffer.push_str(&docs);
let name = item.ident.name.as_str();
buffer.push_str(&format!("typedef enum {} {{\n", name));
if let ast::ItemKind::Enum(ref definition, ref generics) = item.node {
if generics.is_parameterized() {
return Err(Error {
level: Level::Error,
span: Some(item.span),
message: "cheddar can not handle parameterized `#[repr(C)]` enums".into(),
});
}
for var in &definition.variants {
if !var.node.data.is_unit() {
return Err(Error {
level: Level::Error,
span: Some(var.span),
message: "cheddar can not handle `#[repr(C)]` enums with non-unit variants".into(),
});
}
let (_, docs) = parse_attr(&var.node.attrs, |_| true, |attr| retrieve_docstring(attr, "\t"));
buffer.push_str(&docs);
buffer.push_str(&format!("\t{}_{},\n", name, print::pprust::variant_to_string(var)));
}
} else {
return Err(Error {
level: Level::Bug,
span: Some(item.span),
message: "`parse_enum` called on wrong `Item_`".into(),
});
}
buffer.push_str(&format!("}} {};\n\n", name));
Ok(Some(buffer))
}
fn parse_struct(item: &ast::Item) -> Result<Option<String>, Error> {
let (repr_c, docs) = parse_attr(&item.attrs, check_repr_c, |attr| retrieve_docstring(attr, ""));
if !repr_c { return Ok(None); }
let mut buffer = String::new();
buffer.push_str(&docs);
let name = item.ident.name.as_str();
buffer.push_str(&format!("typedef struct {}", name));
if let ast::ItemKind::Struct(ref variants, ref generics) = item.node {
if generics.is_parameterized() {
return Err(Error {
level: Level::Error,
span: Some(item.span),
message: "cheddar can not handle parameterized `#[repr(C)]` structs".into(),
});
}
if variants.is_struct() {
buffer.push_str(" {\n");
for field in variants.fields() {
let (_, docs) = parse_attr(&field.attrs, |_| true, |attr| retrieve_docstring(attr, "\t"));
buffer.push_str(&docs);
let name = match field.ident {
Some(name) => name.name.as_str(),
None => unreachable!("a tuple struct snuck through"),
};
let ty = try_some!(types::rust_to_c(&*field.ty, &name));
buffer.push_str(&format!("\t{};\n", ty));
}
buffer.push_str("}");
} else if variants.is_tuple() && variants.fields().len() == 1 {
} else {
return Err(Error {
level: Level::Error,
span: Some(item.span),
message: "cheddar can not handle unit or tuple `#[repr(C)]` structs with >1 members".into(),
});
}
} else {
return Err(Error {
level: Level::Bug,
span: Some(item.span),
message: "`parse_struct` called on wrong `Item_`".into(),
});
}
buffer.push_str(&format!(" {};\n\n", name));
Ok(Some(buffer))
}
fn parse_fn(item: &ast::Item) -> Result<Option<String>, Error> {
let (no_mangle, docs) = parse_attr(&item.attrs, check_no_mangle, |attr| retrieve_docstring(attr, ""));
if !no_mangle { return Ok(None); }
let mut buffer = String::new();
let name = item.ident.name.as_str();
buffer.push_str(&docs);
if let ast::ItemKind::Fn(ref fn_decl, _, _, abi, ref generics, _) = item.node {
use syntax::abi::Abi;
match abi {
Abi::C | Abi::Cdecl | Abi::Stdcall | Abi::Fastcall | Abi::System => {},
_ => return Ok(None),
}
if generics.is_parameterized() {
return Err(Error {
level: Level::Error,
span: Some(item.span),
message: "cheddar can not handle parameterized extern functions".into(),
});
}
let fn_decl: &ast::FnDecl = &*fn_decl;
let mut buf_without_return = format!("{}(", name);
let has_args = !fn_decl.inputs.is_empty();
for arg in &fn_decl.inputs {
use syntax::ast::{PatKind, BindingMode};
let arg_name = match arg.pat.node {
PatKind::Ident(BindingMode::ByValue(_), ref ident, None) => {
ident.node.name.to_string()
}
_ => return Err(Error {
level: Level::Error,
span: None,
message: format!("cheddar only supports by-value arguments:
incorrect argument `{}` in function definition `{}`",
print::pprust::pat_to_string(&*arg.pat), name),
})
};
let arg_type = try_some!(types::rust_to_c(&*arg.ty, &arg_name));
buf_without_return.push_str(&arg_type);
buf_without_return.push_str(", ");
}
if has_args {
buf_without_return.pop();
buf_without_return.pop();
} else {
buf_without_return.push_str("void");
}
buf_without_return.push(')');
let output_type = &fn_decl.output;
let full_declaration = match *output_type {
ast::FunctionRetTy::Ty(ref ty) if ty.node == ast::TyKind::Never => {
return Err(Error {
level: Level::Error,
span: Some(ty.span),
message: "panics across a C boundary are naughty!".into(),
});
},
ast::FunctionRetTy::Default(..) => format!("void {}", buf_without_return),
ast::FunctionRetTy::Ty(ref ty) => try_some!(types::rust_to_c(&*ty, &buf_without_return)),
};
buffer.push_str(&full_declaration);
buffer.push_str(";\n\n");
} else {
return Err(Error {
level: Level::Bug,
span: Some(item.span),
message: "`parse_fn` called on wrong `Item_`".into(),
});
}
Ok(Some(buffer))
}
fn parse_attr<C, R>(attrs: &[ast::Attribute], check: C, retrieve: R) -> (bool, String)
where C: Fn(&ast::Attribute) -> bool,
R: Fn(&ast::Attribute) -> Option<String>,
{
let mut check_passed = false;
let mut retrieved_str = String::new();
for attr in attrs {
if !check_passed { check_passed = check(attr); }
if let Some(string) = retrieve(attr) { retrieved_str.push_str(&string); }
}
(check_passed, retrieved_str)
}
fn check_repr_c(attr: &ast::Attribute) -> bool {
match attr.value.node {
ast::MetaItemKind::List(ref word) if attr.name() == "repr" => match word.first() {
Some(word) => match word.node {
ast::NestedMetaItemKind::MetaItem(ref item) if item.name == "C" => true,
_ => false,
},
_ => false,
},
_ => false,
}
}
fn check_no_mangle(attr: &ast::Attribute) -> bool {
match attr.value.node {
ast::MetaItemKind::Word if attr.name() == "no_mangle" => true,
_ => false,
}
}
fn retrieve_docstring(attr: &ast::Attribute, prepend: &str) -> Option<String> {
match attr.value.node {
ast::MetaItemKind::NameValue(ref val) if attr.name() == "doc" => match val.node {
ast::LitKind::Str(ref docs, _) => Some(format!("{}{}\n", prepend, docs)),
_ => unreachable!("docs must be literal strings"),
},
_ => None,
}
}