Merge pull request 258 from adetaylor/cxx-embedding-lib
diff --git a/gen/src/mod.rs b/gen/src/mod.rs
index 68c6f85..a939760 100644
--- a/gen/src/mod.rs
+++ b/gen/src/mod.rs
@@ -10,15 +10,19 @@
 #[cfg(test)]
 mod tests;
 
-use self::error::{format_err, Error, Result};
+use self::error::format_err;
+pub use self::error::{Error, Result};
 use self::file::File;
 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;
 
-#[derive(Default)]
-pub(super) struct Opt {
+/// Options for C++ code generation.
+#[derive(Default, Clone)]
+pub struct Opt {
     /// Any additional headers to #include
     pub include: Vec<String>,
     /// Whether to set __attribute__((visibility("default")))
@@ -26,6 +30,14 @@
     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)
@@ -36,26 +48,52 @@
     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)),
     };
-    let mut source = source.as_str();
+    match generate_from_string(&source, opt, header) {
+        Ok(out) => out,
+        Err(err) => format_err(path, &source, err),
+    }
+}
+
+fn generate_from_string(source: &str, opt: Opt, header: bool) -> Result<Vec<u8>> {
+    let mut source = source;
     if source.starts_with("#!") && !source.starts_with("#![") {
         let shebang_end = source.find('\n').unwrap_or(source.len());
         source = &source[shebang_end..];
     }
-    match generate(source, opt, header) {
-        Ok(out) => out,
-        Err(err) => format_err(path, source, err),
+    let syntax: File = syn::parse_str(source)?;
+    let results = generate(syntax, opt, header, !header)?;
+    match results {
+        (Some(hdr), None) => Ok(hdr),
+        (None, Some(cxx)) => Ok(cxx),
+        _ => 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: File = syn::parse_str(source)?;
     let bridge = syntax
         .modules
         .into_iter()
@@ -68,6 +106,18 @@
     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))
 }