Compartmentalises the compilation. `cheddar::parse` is no longer
responsible for include-guards, includes, or extern declarations.
This commit is contained in:
Sean Marshallsay 2016-01-07 23:06:10 +00:00
Родитель 4b927f2d7c
Коммит a06c129848
7 изменённых файлов: 134 добавлений и 105 удалений

Просмотреть файл

@ -50,7 +50,7 @@ pub extern fn datalib_data_f64_append(data: *mut datalib_data_f64, x: f64, y: f6
fn main() {
let header = cheddar::Cheddar::new().expect("failed to read cargo manifest")
.source_string(RUST)
.compile_to_string()
.compile("SOME_HEADER_NAME")
.expect("header could not be compiled");
println!("RUST SOURCE FILE:\n{}\n", RUST);

Просмотреть файл

@ -1,3 +1,4 @@
//! Demonstrates a C API based on the stack.
extern crate cheddar;
const RUST: &'static str = r#"
@ -33,7 +34,7 @@ pub extern fn Student_change_grade(student: Student, changer: extern fn(f64) ->
fn main() {
let header = cheddar::Cheddar::new().expect("failed to read cargo manifest")
.source_string(RUST)
.compile_to_string()
.compile("SOME_HEADER_NAME")
.expect("header could not be compiled");
println!("RUST SOURCE FILE:\n{}\n", RUST);

Просмотреть файл

@ -41,16 +41,8 @@ fn main() {
}
if let Some(output) = matches.value_of("OUTPUT") {
let path = std::path::Path::new(&output);
if let Some(dir) = path.parent() {
cheddar.directory(dir);
}
if let Some(file) = path.file_name() {
cheddar.file(file);
}
}
cheddar.compile();
cheddar.write(&output);
} else {
cheddar.write("cheddar.h");
};
}

Просмотреть файл

@ -10,27 +10,29 @@
//! `build-dependencies` with `dependencies`):
//!
//! ```toml
//! # Cargo.toml
//!
//! [build-dependencies]
//! rusty-cheddar = "0.3"
//! rusty-cheddar = "0.3.0"
//! ```
//!
//! Then create the following `build.rs`:
//!
//! ```no_run
//! // build.rs
//!
//! extern crate cheddar;
//!
//! fn main() {
//! cheddar::Cheddar::new().expect("could not read manifest")
//! .file("my_header.h")
//! .compile();
//! .write("include/my_header.h");
//! }
//! ```
//!
//! This should work as is providing you've set up your project correctly. **Don't forget to add a
//! `build = ...` to your `[package]` section, see [the cargo docs] for more info.**
//!
//! rusty-cheddar will then create a `my_header.h` file in in `$OUT_DIR` where `$OUT_DIR` is set by
//! `cargo` (usually `target/debug/build/{your crate}_{some hash}/out`). Note that rusty-cheddar
//! rusty-cheddar will then create a `my_header.h` file in `include/`. Note that rusty-cheddar
//! emits very few warnings, it is up to the programmer to write a library which can be correctly
//! called from C.
//!
@ -50,9 +52,8 @@
//!
//! fn main() {
//! cheddar::Cheddar::new().expect("could not read manifest")
//! .file("my_header.h")
//! .module("c_api")
//! .compile();
//! .write("target/include/rusty.h");
//! }
//! ```
//!
@ -66,7 +67,7 @@
//! }
//! ```
//!
//! There is also the `.compile_to_string()` method for finer control.
//! There is also the `.compile()` and `.compile_code()` methods for finer control.
//!
//! # Conversions
//!
@ -254,16 +255,11 @@
//! // Some more boilerplate omitted.
//! ```
//!
//! ## Type Conversions
//! ## Paths
//!
//! rusty-cheddar currently does not handle type paths (e.g. `mymod::MyType`), instead they must be `use`ed
//! first:
//!
//! ```ignore
//! // pub type MyCType = mymod::MyType; // This will put `typedef mymod::MyType MyCType;` into the header.
//! use mymod::MyType;
//! pub type MyCType = MyType;
//! ```
//! You must not put types defined in other modules in an exported type signature without hiding it
//! behind an opaque struct. This is because the C compiler must know the layout of the type and
//! rusty-cheddar can not yet search other modules.
//!
//! The very important exception to this rule is `libc`, types used from `libc` _must_ be qualified
//! (e.g. `libc::c_void`) so that they can be converted properly.
@ -297,6 +293,8 @@ mod parse;
pub use syntax::errors::Level;
/// Describes an error encountered by the compiler.
///
/// These can be printed nicely using the `Cheddar::print_err` method.
#[derive(Debug)]
pub struct Error {
pub level: Level,
@ -361,16 +359,8 @@ enum Source {
pub struct Cheddar {
/// The root source file of the crate.
input: Source,
/// The directory in which to place the header file.
///
/// Default is the environment variable `OUT_DIR` when available, otherwise it is the current
/// directory.
outdir: path::PathBuf,
/// The file name of the header file.
///
/// Default is `cheddar.h`.
outfile: path::PathBuf,
// TODO: store this as a syntax::ast::Path when allowing arbitrary modules.
// TODO: this should be part of a ParseOpts struct
/// The module which contains the C API.
module: Option<String>,
/// The current parser session.
@ -388,13 +378,8 @@ impl Cheddar {
let source_path = try!(source_file_from_cargo());
let input = Source::File(path::PathBuf::from(source_path));
let outdir = std::env::var_os("OUT_DIR")
.map_or_else(path::PathBuf::new, path::PathBuf::from);
Ok(Cheddar {
input: input,
outdir: outdir,
outfile: path::PathBuf::from("cheddar.h"),
module: None,
session: syntax::parse::ParseSess::new(),
})
@ -418,28 +403,6 @@ impl Cheddar {
self
}
/// Set the output directory.
///
/// Default is [`OUT_DIR`] when available, otherwise it is the current directory.
///
/// [`OUT_DIR`]: http://doc.crates.io/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
pub fn directory<T>(&mut self, path: T) -> &mut Cheddar
where path::PathBuf: convert::From<T>,
{
self.outdir = path::PathBuf::from(path);
self
}
/// Set the name for the created header file.
///
/// Default is `cheddar.h`.
pub fn file<T>(&mut self, path: T) -> &mut Cheddar
where path::PathBuf: convert::From<T>,
{
self.outfile = path::PathBuf::from(path);
self
}
/// Set the module which contains the header file.
///
/// The module should be described using Rust's path syntax, i.e. in the same way that you
@ -450,18 +413,19 @@ impl Cheddar {
self
}
/// Compile the header into a string.
/// Compile just the code into header declarations.
///
/// Returns a vector of errors which can be printed using `Cheddar::print_error`.
pub fn compile_to_string(&self) -> Result<String, Vec<Error>> {
/// This does not add any include-guards, includes, or extern declarations. It is mainly
/// intended for internal use, but may be of interest to people who wish to embed
/// rusty-cheddar's generated code in another file.
pub fn compile_code(&self) -> Result<String, Vec<Error>> {
let sess = &self.session;
let file_name = self.outfile.file_stem().and_then(|p| p.to_str()).unwrap_or("default");
let krate = match self.input {
Source::File(ref path) => syntax::parse::parse_crate_from_file(path, vec![], sess),
Source::String(ref source) => syntax::parse::parse_crate_from_source_str(
"cheddar_source".to_owned(),
// TODO: this clone could be quite costly, maybe rethink this design?
// or just use a slice.
source.clone(),
vec![],
sess,
@ -469,12 +433,53 @@ impl Cheddar {
};
if let Some(ref module) = self.module {
parse::parse_crate(&krate, module, &file_name)
parse::parse_crate(&krate, module)
} else {
parse::parse_mod(&krate.module, &file_name)
parse::parse_mod(&krate.module)
}
}
/// Compile the header declarations then add the needed `#include`s.
///
/// Currently includes:
///
/// - `stdint.h`
/// - `stdbool.h`
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))
}
/// Compile a header while conforming to C89 (or ANSI C).
///
/// This does not include `stdint.h` or `stdbool.h` and also wraps single line comments with
/// `/*` and `*/`.
///
/// `id` is used to help generate the include guard and may be empty.
///
/// # TODO
///
/// This is intended to be a public API, but currently comments are not handled correctly so it
/// is being kept private.
///
/// The parser should warn on uses of `bool` or fixed-width integers (`i16`, `u32`, etc.).
#[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))
}
/// Compile a header.
///
/// `id` is used to help generate the include guard and may be empty.
pub fn compile(&self, id: &str) -> Result<String, Vec<Error>> {
let code = try!(self.compile_with_includes());
Ok(wrap_guard(&wrap_extern(&code), id))
}
/// Write the header to a file.
///
/// This is a convenience method for use in build scripts. If errors occur during compilation
@ -483,20 +488,24 @@ impl Cheddar {
/// # Panics
///
/// Panics on any compilation error so that the build script exits.
pub fn compile(&self) {
pub fn write<P: AsRef<path::Path>>(&self, file: P) {
let file = file.as_ref();
let sess = &self.session;
if let Err(error) = std::fs::create_dir_all(&self.outdir) {
sess.span_diagnostic.err(&format!(
"could not create directories in '{}': {}",
self.outdir.display(),
error,
));
if let Some(dir) = file.parent() {
if let Err(error) = std::fs::create_dir_all(dir) {
sess.span_diagnostic.err(&format!(
"could not create directories in '{}': {}",
dir.display(),
error,
));
panic!("errors compiling header file");
panic!("errors compiling header file");
}
}
let header = match self.compile_to_string() {
let file_name = file.file_name().map(|os| os.to_string_lossy()).unwrap_or("default".into());
let header = match self.compile(&file_name) {
Ok(header) => header,
Err(errors) => {
for error in errors {
@ -508,7 +517,6 @@ impl Cheddar {
};
let file = self.outdir.join(&self.outfile);
let bytes_buf = header.into_bytes();
if let Err(error) = std::fs::File::create(&file).and_then(|mut f| f.write_all(&bytes_buf)) {
sess.span_diagnostic.err(&format!("could not write to '{}': {}", file.display(), error));
@ -562,3 +570,30 @@ fn source_file_from_cargo() -> std::result::Result<String, Error> {
.unwrap_or(default)
.into())
}
/// Wrap a block of code with an extern declaration.
fn wrap_extern(code: &str) -> String {
format!(r#"
#ifdef __cplusplus
extern "C" {{
#endif
{}
#ifdef __cplusplus
}}
#endif
"#, code)
}
/// Wrap a block of code with an include-guard.
fn wrap_guard(code: &str, id: &str) -> String {
format!(r"
#ifndef cheddar_generated_{0}_h
#define cheddar_generated_{0}_h
{1}
#endif
", id, code)
}

Просмотреть файл

@ -7,12 +7,21 @@ use types;
use Error;
use Level;
// TODO: we should use our own parse state which tracks what types are callable from C
// - then we can give decent errors for ones that aren't
// - will require multiple passes of the module
// - each time a struct, enum, or typedef changes do another pass
// - only store errors on the final pass
// - maybe there will be some errors which will need to be stored before then
// - search inside struct as well for whitelisted types
// - possibly also search other crates when encountering a path
/// The main entry point when looking for a specific module.
///
/// Determines which module to parse, ensures it is `pub use`ed then hands off to
/// `Cheddar::parse_mod`.
pub fn parse_crate(krate: &ast::Crate, module: &str, file_name: &str) -> Result<String, Vec<Error>> {
/// `cheddar::parse::parse_mod`.
pub fn parse_crate(krate: &ast::Crate, module: &str) -> Result<String, Vec<Error>> {
let mut mod_item = None;
let mut pub_used = false;
@ -43,7 +52,7 @@ pub fn parse_crate(krate: &ast::Crate, module: &str, file_name: &str) -> Result<
if let Some(mod_item) = mod_item {
if pub_used {
parse_mod(&mod_item, file_name)
parse_mod(&mod_item)
} else {
Err(vec![Error {
level: Level::Error,
@ -64,11 +73,8 @@ pub fn parse_crate(krate: &ast::Crate, module: &str, file_name: &str) -> Result<
///
/// Iterates through all items in the module and dispatches to correct methods, then pulls all
/// the results together into a header.
pub fn parse_mod(module: &ast::Mod, file_name: &str) -> Result<String, Vec<Error>> {
let mut buffer = format!("#ifndef cheddar_gen_{0}_h\n#define cheddar_gen_{0}_h\n\n", file_name);
buffer.push_str("#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n");
buffer.push_str("#include <stdint.h>\n#include <stdbool.h>\n\n");
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 it's not visible it can't be called from C.
@ -95,9 +101,6 @@ pub fn parse_mod(module: &ast::Mod, file_name: &str) -> Result<String, Vec<Error
};
}
buffer.push_str("#ifdef __cplusplus\n}\n#endif\n\n");
buffer.push_str("#endif\n");
if errors.is_empty() {
Ok(buffer)
} else {

Просмотреть файл

@ -7,9 +7,11 @@ use syntax::print;
use Error;
use Level;
// TODO: C function pointers _must_ have a name associated with them but this Option business feels
// like a shit way to handle that
// - maybe have a named_rust_to_c which allows fn_pointers and rust_to_c doesn't?
// TODO: don't pass a Ty and a &str, pass
// enum Type<'t, 'n> {
// Named(&'t ast::Ty, &'n name),
// Unnamed(&'t ast::Ty),
// }
/// Turn a Rust (type, name) pair into a C (type, name) pair.
///
/// If name is `None` then there is no name associated with that type.

Просмотреть файл

@ -6,11 +6,7 @@ macro_rules! inner_cheddar_cmp_test {
($name:ident, $compile:expr, $header:expr) => {
#[test]
fn $name() {
let expected = concat!(
// Due to the way CppHeaderParser works we only need to add the #define.
"#define cheddar_gen_cheddar_h\n",
$header,
);
let expected = $header;
let actual = match $compile {
Ok(actual) => actual,
@ -84,7 +80,7 @@ macro_rules! cheddar_cmp_test {
($name:ident, $api:expr, $header:expr, $rust:expr) => {
inner_cheddar_cmp_test! {
$name,
cheddar::Cheddar::new().unwrap().source_string($rust).module($api).compile_to_string(),
cheddar::Cheddar::new().unwrap().source_string($rust).module($api).compile_code(),
$header
}
};
@ -92,7 +88,7 @@ macro_rules! cheddar_cmp_test {
($name:ident, $header:expr, $rust:expr) => {
inner_cheddar_cmp_test! {
$name,
cheddar::Cheddar::new().unwrap().source_string($rust).compile_to_string(),
cheddar::Cheddar::new().unwrap().source_string($rust).compile_code(),
$header
}
};