зеркало из https://github.com/mozilla/moz-cheddar.git
Brand spanking new API.
Compartmentalises the compilation. `cheddar::parse` is no longer responsible for include-guards, includes, or extern declarations.
This commit is contained in:
Родитель
4b927f2d7c
Коммит
a06c129848
|
@ -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");
|
||||
};
|
||||
}
|
||||
|
|
175
src/lib.rs
175
src/lib.rs
|
@ -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)
|
||||
}
|
||||
|
|
25
src/parse.rs
25
src/parse.rs
|
@ -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.
|
||||
|
|
10
tests/lib.rs
10
tests/lib.rs
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче