#![cfg_attr(not(feature = "with-syntex"), feature(rustc_private))]
#[cfg(feature = "with-syntex")]
extern crate syntex_errors as errors;
#[cfg(not(feature = "with-syntex"))]
extern crate rustc_errors as errors;
#[cfg(feature = "with-syntex")]
extern crate syntex_syntax as syntax;
#[cfg(not(feature = "with-syntex"))]
extern crate syntax;
extern crate toml;
use std::convert;
use std::io::Read;
use std::io::Write;
use std::path;
macro_rules! try_some {
($expr:expr) => {{ match $expr {
Ok(Some(val)) => val,
expr => return expr,
}}};
}
mod types;
mod parse;
pub use errors::Level;
#[derive(Debug)]
pub struct Error {
pub level: Level,
span: Option<syntax::codemap::Span>,
pub message: String,
}
impl std::fmt::Display for Error {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "{}: {}", self.level, self.message)
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
match self.level {
Level::Bug => "internal error",
Level::Fatal | Level::Error => "error",
Level::Warning => "warning",
Level::Note => "note",
Level::Help => "help",
_ => unreachable!(),
}
}
}
impl Error {
#[allow(unused_must_use)]
fn print(&self, sess: &syntax::parse::ParseSess) {
if let Some(span) = self.span {
match self.level {
Level::Bug => { sess.span_diagnostic.span_bug(span, &self.message); },
Level::Fatal => { sess.span_diagnostic.span_fatal(span, &self.message); },
Level::Error => { sess.span_diagnostic.span_err(span, &self.message); },
Level::Warning => { sess.span_diagnostic.span_warn(span, &self.message); },
Level::Note => { sess.span_diagnostic.span_note_without_error(span, &self.message); },
Level::Help => { sess.span_diagnostic.struct_dummy().span_help(span, &self.message); },
_ => unreachable!(),
};
} else {
match self.level {
Level::Bug => { sess.span_diagnostic.bug(&self.message); },
Level::Fatal => { sess.span_diagnostic.fatal(&self.message); },
Level::Error => { sess.span_diagnostic.err(&self.message); },
Level::Warning => { sess.span_diagnostic.warn(&self.message); },
Level::Note => { sess.span_diagnostic.note_without_error(&self.message); },
Level::Help => { sess.span_diagnostic.struct_dummy().help(&self.message); },
_ => unreachable!(),
};
}
}
}
enum Source {
String(String),
File(path::PathBuf),
}
pub struct Cheddar {
input: Source,
module: Option<syntax::ast::Path>,
custom_code: String,
session: syntax::parse::ParseSess,
}
impl Cheddar {
pub fn new() -> std::result::Result<Cheddar, Error> {
let source_path = try!(source_file_from_cargo());
let input = Source::File(path::PathBuf::from(source_path));
Ok(Cheddar {
input: input,
module: None,
custom_code: String::new(),
session: syntax::parse::ParseSess::new(),
})
}
pub fn source_file<T>(&mut self, path: T) -> &mut Cheddar
where path::PathBuf: convert::From<T>,
{
self.input = Source::File(path::PathBuf::from(path));
self
}
pub fn source_string(&mut self, source: &str) -> &mut Cheddar {
self.input = Source::String(source.to_owned());
self
}
pub fn module(&mut self, module: &str) -> Result<&mut Cheddar, Vec<Error>> {
let sess = syntax::parse::ParseSess::new();
let result = {
let mut parser = ::syntax::parse::new_parser_from_source_str(
&sess,
"".into(),
module.into(),
);
parser.parse_path(syntax::parse::parser::PathStyle::Mod)
};
if let Ok(path) = result {
self.module = Some(path);
Ok(self)
} else {
Err(vec![Error {
level: Level::Fatal,
span: None,
message: format!("malformed module path `{}`", module),
}])
}
}
pub fn insert_code(&mut self, code: &str) -> &mut Cheddar {
self.custom_code.push_str(code);
self
}
pub fn compile_code(&self) -> Result<String, Vec<Error>> {
let sess = &self.session;
let krate = match self.input {
Source::File(ref path) => syntax::parse::parse_crate_from_file(path, sess),
Source::String(ref source) => syntax::parse::parse_crate_from_source_str(
"cheddar_source".to_owned(),
source.clone(),
sess,
),
}.unwrap();
if let Some(ref module) = self.module {
parse::parse_crate(&krate, module)
} else {
parse::parse_mod(&krate.module)
}.map(|source| format!("{}\n\n{}", self.custom_code, source))
}
fn compile_with_includes(&self) -> Result<String, Vec<Error>> {
let code = try!(self.compile_code());
Ok(format!("#include <stdint.h>\n#include <stdbool.h>\n\n{}", code))
}
#[allow(dead_code)]
fn compile_c89(&self, id: &str) -> Result<String, Vec<Error>> {
let code = try!(self.compile_code());
Ok(wrap_guard(&wrap_extern(&code), id))
}
pub fn compile(&self, id: &str) -> Result<String, Vec<Error>> {
let code = try!(self.compile_with_includes());
Ok(wrap_guard(&wrap_extern(&code), id))
}
pub fn write<P: AsRef<path::Path>>(&self, file: P) -> Result<(), Vec<Error>> {
let file = file.as_ref();
if let Some(dir) = file.parent() {
if let Err(error) = std::fs::create_dir_all(dir) {
return Err(vec![Error {
level: Level::Fatal,
span: None,
message: format!("could not create directories in '{}': {}", dir.display(), error),
}]);
}
}
let file_name = file.file_stem().map_or("default".into(), |os| os.to_string_lossy());
let header = try!(self.compile(&file_name));
let bytes_buf = header.into_bytes();
if let Err(error) = std::fs::File::create(&file).and_then(|mut f| f.write_all(&bytes_buf)) {
Err(vec![Error {
level: Level::Fatal,
span: None,
message: format!("could not write to '{}': {}", file.display(), error),
}])
} else {
Ok(())
}
}
pub fn run_build<P: AsRef<path::Path>>(&self, file: P) {
if let Err(errors) = self.write(file) {
for error in &errors {
self.print_error(error);
}
panic!("errors compiling header file");
}
}
pub fn print_error(&self, error: &Error) {
error.print(&self.session);
}
}
fn source_file_from_cargo() -> std::result::Result<String, Error> {
let cargo_toml = path::Path::new(
&std::env::var_os("CARGO_MANIFEST_DIR")
.unwrap_or(std::ffi::OsString::from(""))
).join("Cargo.toml");
let default = "src/lib.rs";
let mut cargo_toml = match std::fs::File::open(&cargo_toml) {
Ok(value) => value,
Err(..) => return Ok(default.to_owned()),
};
let mut buf = String::new();
match cargo_toml.read_to_string(&mut buf) {
Ok(..) => {},
Err(..) => return Err(Error {
level: Level::Fatal,
span: None,
message: "could not read cargo manifest".into(),
}),
};
let table = match (&buf).parse::<toml::Value>() {
Ok(value) => value,
Err(..) => return Err(Error {
level: Level::Fatal,
span: None,
message: "could not parse cargo manifest".into(),
}),
};
Ok(table.get("lib")
.and_then(|t| t.get("path"))
.and_then(|s| s.as_str())
.unwrap_or(default)
.into())
}
fn wrap_extern(code: &str) -> String {
format!(r#"
#ifdef __cplusplus
extern "C" {{
#endif
{}
#ifdef __cplusplus
}}
#endif
"#, code)
}
fn wrap_guard(code: &str, id: &str) -> String {
format!(r"
#ifndef cheddar_generated_{0}_h
#define cheddar_generated_{0}_h
{1}
#endif
", sanitise_id(id), code)
}
fn sanitise_id(id: &str) -> String {
id.chars().filter(|ch| ch.is_digit(36) || *ch == '_').collect()
}
#[cfg(test)]
mod test {
#[test]
fn sanitise_id() {
assert!(super::sanitise_id("") == "");
assert!(super::sanitise_id("!@£$%^&*()_+") == "_");
assert!(super::sanitise_id("filename.h") == "filenameh");
}
}