Adding library for high-level code generators.

The intention here is to provide a way for high level code generators
to convert a Rust TokenStream into C++ bindings code without the need
to write files to disk or invoke an external command.
This commit is contained in:
Adrian Taylor 2020-08-14 10:51:00 -07:00
parent 291a8b87a1
commit 9fc0846261
10 changed files with 107 additions and 14 deletions

View File

@ -32,7 +32,7 @@ rustversion = "1.0"
trybuild = { version = "1.0.32", features = ["diff"] }
[workspace]
members = ["demo-rs", "gen/build", "gen/cmd", "macro", "tests/ffi"]
members = ["demo-rs", "gen/build", "gen/cmd", "gen/lib", "macro", "tests/ffi"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,4 +1,5 @@
This directory contains CXX's C++ code generator. This code generator has two
This directory contains CXX's C++ code generator. This code generator has three
public frontends, one a command-line application (binary) in the *cmd* directory
and the other a library intended to be used from a build.rs in the *build*
directory.
a library intended to be used from a build.rs in the *build* directory, and
a library intended to be used from arbitrary other places (e.g. higher-level
code generators) in the *lib* directory.

21
gen/lib/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "cxx-gen"
version = "0.3.4"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "C++ code generator for integrating `cxx` crate into higher level tools."
repository = "https://github.com/dtolnay/cxx"
keywords = ["ffi"]
categories = ["development-tools::ffi"]
[dependencies]
anyhow = "1.0"
cc = "1.0.49"
codespan-reporting = "0.9"
proc-macro2 = { version = "1.0.17", default-features = false, features = ["span-locations"] }
quote = { version = "1.0", default-features = false }
syn = { version = "1.0.20", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

1
gen/lib/LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE-APACHE

1
gen/lib/LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE-MIT

1
gen/lib/src/gen Symbolic link
View File

@ -0,0 +1 @@
../../src

17
gen/lib/src/lib.rs Normal file
View File

@ -0,0 +1,17 @@
//! The CXX code generator for constructing and compiling C++ code.
//!
//! This is intended to be embedded into higher-level code generators.
mod gen;
mod syntax;
use crate::gen::Opt;
use proc_macro2::TokenStream;
pub use crate::gen::{Error, Result, GeneratedCode};
/// Generate C++ bindings code from a Rust token stream. This should be a Rust
/// token stream which somewhere contains a `#[cxx::bridge] mod {}`.
pub fn generate_header_and_cc(rust_source: TokenStream) -> Result<GeneratedCode> {
gen::do_generate_from_tokens(rust_source, Opt::default())
}

1
gen/lib/src/syntax Symbolic link
View File

@ -0,0 +1 @@
../../../syntax

View File

@ -11,13 +11,18 @@ use std::ops::Range;
use std::path::Path;
use std::process;
pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
pub(super) enum Error {
pub enum Error {
/// No `#[cxx::bridge]` module could be found.
NoBridgeMod,
/// `#[cxx::bridge]` was attached to something other than
/// an inline module.
OutOfLineMod,
/// An IO error occurred when reading Rust code.
Io(io::Error),
/// A syntax error occurred when parsing Rust code.
Syn(syn::Error),
}

View File

@ -10,20 +10,23 @@ mod write;
#[cfg(test)]
mod tests;
use self::error::{format_err, Error, Result};
use self::error::format_err;
pub use self::error::{Error, Result};
use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::{self, check, Types};
use proc_macro2::TokenStream;
use std::clone::Clone;
use std::fs;
use std::path::Path;
use syn::Item;
use syn::{File, Item};
struct Input {
namespace: Namespace,
module: Vec<Item>,
}
#[derive(Default)]
#[derive(Default, Clone)]
pub(super) struct Opt {
/// Any additional headers to #include
pub include: Vec<String>,
@ -32,6 +35,14 @@ pub(super) struct Opt {
pub cxx_impl_annotations: Option<String>,
}
/// Results of code generation.
pub struct GeneratedCode {
/// The bytes of a C++ header file.
pub header: Vec<u8>,
/// The bytes of a C++ implementation file (e.g. .cc, cpp etc.)
pub cxx: Vec<u8>,
}
pub(super) fn do_generate_bridge(path: &Path, opt: Opt) -> Vec<u8> {
let header = false;
generate_from_path(path, opt, header)
@ -42,21 +53,43 @@ pub(super) fn do_generate_header(path: &Path, opt: Opt) -> Vec<u8> {
generate_from_path(path, opt, header)
}
pub(super) fn do_generate_from_tokens(
tokens: TokenStream,
opt: Opt,
) -> std::result::Result<GeneratedCode, Error> {
let syntax = syn::parse2::<File>(tokens)?;
match generate(syntax, opt, true, true) {
Ok((Some(header), Some(cxx))) => Ok(GeneratedCode { header, cxx } ),
Err(err) => Err(err),
_ => panic!("Unexpected generation"),
}
}
fn generate_from_path(path: &Path, opt: Opt, header: bool) -> Vec<u8> {
let source = match fs::read_to_string(path) {
Ok(source) => source,
Err(err) => format_err(path, "", Error::Io(err)),
};
match generate(&source, opt, header) {
let syntax = match syn::parse_file(&source) {
Ok(out) => out,
Err(err) => format_err(path, "", Error::Syn(err)),
};
match generate(syntax, opt, header, !header) {
Ok((Some(hdr), None)) => hdr,
Ok((None, Some(cxx))) => cxx,
Err(err) => format_err(path, &source, err),
_ => panic!("Unexpected generation"),
}
}
fn generate(source: &str, opt: Opt, header: bool) -> Result<Vec<u8>> {
fn generate(
syntax: File,
opt: Opt,
gen_header: bool,
gen_cxx: bool,
) -> Result<(Option<Vec<u8>>, Option<Vec<u8>>)> {
proc_macro2::fallback::force();
let ref mut errors = Errors::new();
let syntax = syn::parse_file(&source)?;
let bridge = find::find_bridge_mod(syntax)?;
let ref namespace = bridge.namespace;
let ref apis = syntax::parse_items(errors, bridge.module);
@ -64,6 +97,18 @@ fn generate(source: &str, opt: Opt, header: bool) -> Result<Vec<u8>> {
errors.propagate()?;
check::typecheck(errors, namespace, apis, types);
errors.propagate()?;
let out = write::gen(namespace, apis, types, opt, header);
Ok(out.content())
// Some callers may wish to generate both header and C++
// from the same token stream to avoid parsing twice. But others
// only need to generate one or the other.
let hdr = if gen_header {
Some(write::gen(namespace, apis, types, opt.clone(), true).content())
} else {
None
};
let cxx = if gen_cxx {
Some(write::gen(namespace, apis, types, opt, false).content())
} else {
None
};
Ok((hdr, cxx))
}