Squashed 'third_party/autocxx/' content from commit 629e8fa53

git-subtree-dir: third_party/autocxx
git-subtree-split: 629e8fa531a633164c0b52e2a3cab536d4cd0849
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I62a03b0049f49adf029e0204639cdb5468dde1a1
diff --git a/parser/Cargo.toml b/parser/Cargo.toml
new file mode 100644
index 0000000..481f21d
--- /dev/null
+++ b/parser/Cargo.toml
@@ -0,0 +1,36 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+# option. This file may not be copied, modified, or distributed
+# except according to those terms.
+
+[package]
+name = "autocxx-parser"
+version = "0.22.0"
+authors = ["Adrian Taylor <adetaylor@chromium.org>"]
+license = "MIT OR Apache-2.0"
+description = "Safe autogenerated interop between Rust and C++"
+repository = "https://github.com/google/autocxx"
+edition = "2021"
+keywords = ["ffi"]
+categories = ["development-tools::ffi", "api-bindings"]
+
+[dependencies]
+log = "0.4"
+proc-macro2 = "1.0"
+quote = "1.0"
+serde = { version = "1.0", features = [ "derive" ]}
+thiserror = "1.0"
+once_cell = "1.10"
+itertools = "0.10.3"
+indexmap = { version="1.8", features = ["serde"]}
+serde_json = "1.0"
+
+[dependencies.syn]
+version = "1.0.39"
+features = [ "full", "extra-traits" ]
+
+[features]
+reproduction_case = []
diff --git a/parser/README.md b/parser/README.md
new file mode 100644
index 0000000..9b91d4a
--- /dev/null
+++ b/parser/README.md
@@ -0,0 +1 @@
+This crate is a [component of autocxx](https://google.github.io/autocxx/).
diff --git a/parser/src/config.rs b/parser/src/config.rs
new file mode 100644
index 0000000..4204acf
--- /dev/null
+++ b/parser/src/config.rs
@@ -0,0 +1,500 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+use std::borrow::Cow;
+use std::collections::hash_map::DefaultHasher;
+use std::hash::{Hash, Hasher};
+
+use itertools::Itertools;
+use proc_macro2::Span;
+use quote::ToTokens;
+
+#[cfg(feature = "reproduction_case")]
+use quote::format_ident;
+use syn::{
+    parse::{Parse, ParseStream},
+    Signature, Token, TypePath,
+};
+use syn::{Ident, Result as ParseResult};
+use thiserror::Error;
+
+use crate::{directives::get_directives, RustPath};
+
+use quote::quote;
+
+#[derive(PartialEq, Clone, Debug, Hash)]
+pub enum UnsafePolicy {
+    AllFunctionsSafe,
+    AllFunctionsUnsafe,
+}
+
+impl Default for UnsafePolicy {
+    fn default() -> Self {
+        Self::AllFunctionsUnsafe
+    }
+}
+
+impl Parse for UnsafePolicy {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        if input.parse::<Option<Token![unsafe]>>()?.is_some() {
+            return Ok(UnsafePolicy::AllFunctionsSafe);
+        }
+        let r = match input.parse::<Option<syn::Ident>>()? {
+            Some(id) => {
+                if id == "unsafe_ffi" {
+                    Ok(UnsafePolicy::AllFunctionsSafe)
+                } else {
+                    Err(syn::Error::new(id.span(), "expected unsafe_ffi"))
+                }
+            }
+            None => Ok(UnsafePolicy::AllFunctionsUnsafe),
+        };
+        if !input.is_empty() {
+            return Err(syn::Error::new(
+                Span::call_site(),
+                "unexpected tokens within safety directive",
+            ));
+        }
+        r
+    }
+}
+
+impl ToTokens for UnsafePolicy {
+    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+        if *self == UnsafePolicy::AllFunctionsSafe {
+            tokens.extend(quote! { unsafe })
+        }
+    }
+}
+
+/// An entry in the allowlist.
+#[derive(Hash, Debug)]
+pub enum AllowlistEntry {
+    Item(String),
+    Namespace(String),
+}
+
+impl AllowlistEntry {
+    fn to_bindgen_item(&self) -> String {
+        match self {
+            AllowlistEntry::Item(i) => i.clone(),
+            AllowlistEntry::Namespace(ns) => format!("{}::.*", ns),
+        }
+    }
+}
+
+/// Allowlist configuration.
+#[derive(Hash, Debug)]
+pub enum Allowlist {
+    Unspecified(Vec<AllowlistEntry>),
+    All,
+    Specific(Vec<AllowlistEntry>),
+}
+
+/// Errors that may be encountered while adding allowlist entries.
+#[derive(Error, Debug)]
+pub enum AllowlistErr {
+    #[error("Conflict between generate/generate_ns! and generate_all! - use one not both")]
+    ConflictingGenerateAndGenerateAll,
+}
+
+impl Allowlist {
+    pub fn push(&mut self, item: AllowlistEntry) -> Result<(), AllowlistErr> {
+        match self {
+            Allowlist::Unspecified(ref mut uncommitted_list) => {
+                let new_list = uncommitted_list
+                    .drain(..)
+                    .chain(std::iter::once(item))
+                    .collect();
+                *self = Allowlist::Specific(new_list);
+            }
+            Allowlist::All => {
+                return Err(AllowlistErr::ConflictingGenerateAndGenerateAll);
+            }
+            Allowlist::Specific(list) => list.push(item),
+        };
+        Ok(())
+    }
+
+    pub(crate) fn set_all(&mut self) -> Result<(), AllowlistErr> {
+        if matches!(self, Allowlist::Specific(..)) {
+            return Err(AllowlistErr::ConflictingGenerateAndGenerateAll);
+        }
+        *self = Allowlist::All;
+        Ok(())
+    }
+}
+
+#[allow(clippy::derivable_impls)] // nightly-only
+impl Default for Allowlist {
+    fn default() -> Self {
+        Allowlist::Unspecified(Vec::new())
+    }
+}
+
+#[derive(Debug, Hash)]
+pub struct Subclass {
+    pub superclass: String,
+    pub subclass: Ident,
+}
+
+#[derive(Clone, Hash)]
+pub struct RustFun {
+    pub path: RustPath,
+    pub sig: Signature,
+    pub receiver: Option<Ident>,
+}
+
+impl std::fmt::Debug for RustFun {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("RustFun")
+            .field("path", &self.path)
+            .field("sig", &self.sig.to_token_stream().to_string())
+            .finish()
+    }
+}
+
+#[derive(Debug, Clone, Hash)]
+pub struct ExternCppType {
+    pub rust_path: TypePath,
+    pub opaque: bool,
+}
+
+/// Newtype wrapper so we can implement Hash.
+#[derive(Debug, Default)]
+pub struct ExternCppTypeMap(pub HashMap<String, ExternCppType>);
+
+impl std::hash::Hash for ExternCppTypeMap {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        for (k, v) in &self.0 {
+            k.hash(state);
+            v.hash(state);
+        }
+    }
+}
+
+/// Newtype wrapper so we can implement Hash.
+#[derive(Debug, Default)]
+pub struct ConcretesMap(pub HashMap<String, Ident>);
+
+impl std::hash::Hash for ConcretesMap {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        for (k, v) in &self.0 {
+            k.hash(state);
+            v.hash(state);
+        }
+    }
+}
+
+#[derive(Debug, Default, Hash)]
+pub struct IncludeCppConfig {
+    pub inclusions: Vec<String>,
+    pub unsafe_policy: UnsafePolicy,
+    pub parse_only: bool,
+    pub exclude_impls: bool,
+    pub(crate) pod_requests: Vec<String>,
+    pub allowlist: Allowlist,
+    pub(crate) blocklist: Vec<String>,
+    pub(crate) constructor_blocklist: Vec<String>,
+    pub instantiable: Vec<String>,
+    pub(crate) exclude_utilities: bool,
+    pub(crate) mod_name: Option<Ident>,
+    pub rust_types: Vec<RustPath>,
+    pub subclasses: Vec<Subclass>,
+    pub extern_rust_funs: Vec<RustFun>,
+    pub concretes: ConcretesMap,
+    pub externs: ExternCppTypeMap,
+}
+
+impl Parse for IncludeCppConfig {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        let mut config = IncludeCppConfig::default();
+
+        while !input.is_empty() {
+            let has_hexathorpe = input.parse::<Option<syn::token::Pound>>()?.is_some();
+            let ident: syn::Ident = input.parse()?;
+            let args;
+            let (possible_directives, to_parse, parse_completely) = if has_hexathorpe {
+                (&get_directives().need_hexathorpe, input, false)
+            } else {
+                input.parse::<Option<syn::token::Bang>>()?;
+                syn::parenthesized!(args in input);
+                (&get_directives().need_exclamation, &args, true)
+            };
+            let all_possible = possible_directives.keys().join(", ");
+            let ident_str = ident.to_string();
+            match possible_directives.get(&ident_str) {
+                None => {
+                    return Err(syn::Error::new(
+                        ident.span(),
+                        format!("expected {}", all_possible),
+                    ));
+                }
+                Some(directive) => directive.parse(to_parse, &mut config, &ident.span())?,
+            }
+            if parse_completely && !to_parse.is_empty() {
+                return Err(syn::Error::new(
+                    ident.span(),
+                    format!("found unexpected input within the directive {}", ident_str),
+                ));
+            }
+            if input.is_empty() {
+                break;
+            }
+        }
+        Ok(config)
+    }
+}
+
+impl IncludeCppConfig {
+    pub fn get_pod_requests(&self) -> &[String] {
+        &self.pod_requests
+    }
+
+    pub fn get_mod_name(&self) -> Ident {
+        self.mod_name
+            .as_ref()
+            .cloned()
+            .unwrap_or_else(|| Ident::new("ffi", Span::call_site()))
+    }
+
+    /// Whether to avoid generating the standard helpful utility
+    /// functions which we normally include in every mod.
+    pub fn exclude_utilities(&self) -> bool {
+        self.exclude_utilities
+    }
+
+    /// Items which the user has explicitly asked us to generate;
+    /// we should raise an error if we weren't able to do so.
+    pub fn must_generate_list(&self) -> Box<dyn Iterator<Item = String> + '_> {
+        if let Allowlist::Specific(items) = &self.allowlist {
+            Box::new(
+                items
+                    .iter()
+                    .filter_map(|i| match i {
+                        AllowlistEntry::Item(i) => Some(i),
+                        AllowlistEntry::Namespace(_) => None,
+                    })
+                    .chain(self.pod_requests.iter())
+                    .cloned(),
+            )
+        } else {
+            Box::new(self.pod_requests.iter().cloned())
+        }
+    }
+
+    /// The allowlist of items to be passed into bindgen, if any.
+    pub fn bindgen_allowlist(&self) -> Option<Box<dyn Iterator<Item = String> + '_>> {
+        match &self.allowlist {
+            Allowlist::All => None,
+            Allowlist::Specific(items) => Some(Box::new(
+                items
+                    .iter()
+                    .map(AllowlistEntry::to_bindgen_item)
+                    .chain(self.pod_requests.iter().cloned())
+                    .chain(self.active_utilities())
+                    .chain(self.subclasses.iter().flat_map(|sc| {
+                        [
+                            format!("{}Cpp", sc.subclass),
+                            sc.subclass.to_string(), // TODO may not be necessary
+                            sc.superclass.clone(),
+                        ]
+                    })),
+            )),
+            Allowlist::Unspecified(_) => unreachable!(),
+        }
+    }
+
+    fn active_utilities(&self) -> Vec<String> {
+        if self.exclude_utilities {
+            Vec::new()
+        } else {
+            vec![self.get_makestring_name()]
+        }
+    }
+
+    fn is_subclass_or_superclass(&self, cpp_name: &str) -> bool {
+        self.subclasses
+            .iter()
+            .flat_map(|sc| {
+                [
+                    Cow::Owned(sc.subclass.to_string()),
+                    Cow::Borrowed(&sc.superclass),
+                ]
+            })
+            .any(|item| cpp_name == item.as_str())
+    }
+
+    /// Whether this type is on the allowlist specified by the user.
+    ///
+    /// A note on the allowlist handling in general. It's used in two places:
+    /// 1) As directives to bindgen
+    /// 2) After bindgen has generated code, to filter the APIs which
+    ///    we pass to cxx.
+    /// This second pass may seem redundant. But sometimes bindgen generates
+    /// unnecessary stuff.
+    pub fn is_on_allowlist(&self, cpp_name: &str) -> bool {
+        self.active_utilities().iter().any(|item| *item == cpp_name)
+            || self.is_subclass_or_superclass(cpp_name)
+            || self.is_subclass_holder(cpp_name)
+            || self.is_subclass_cpp(cpp_name)
+            || self.is_rust_fun(cpp_name)
+            || self.is_concrete_type(cpp_name)
+            || match &self.allowlist {
+                Allowlist::Unspecified(_) => panic!("Eek no allowlist yet"),
+                Allowlist::All => true,
+                Allowlist::Specific(items) => items.iter().any(|entry| match entry {
+                    AllowlistEntry::Item(i) => i == cpp_name,
+                    AllowlistEntry::Namespace(ns) => cpp_name.starts_with(ns),
+                }),
+            }
+    }
+
+    pub fn is_on_blocklist(&self, cpp_name: &str) -> bool {
+        self.blocklist.contains(&cpp_name.to_string())
+    }
+
+    pub fn is_on_constructor_blocklist(&self, cpp_name: &str) -> bool {
+        self.constructor_blocklist.contains(&cpp_name.to_string())
+    }
+
+    pub fn get_blocklist(&self) -> impl Iterator<Item = &String> {
+        self.blocklist.iter()
+    }
+
+    fn is_concrete_type(&self, cpp_name: &str) -> bool {
+        self.concretes.0.values().any(|val| *val == cpp_name)
+    }
+
+    /// Get a hash of the contents of this `include_cpp!` block.
+    pub fn get_hash(&self) -> u64 {
+        let mut s = DefaultHasher::new();
+        self.hash(&mut s);
+        s.finish()
+    }
+
+    /// In case there are multiple sets of ffi mods in a single binary,
+    /// endeavor to return a name which can be used to make symbols
+    /// unique.
+    pub fn uniquify_name_per_mod(&self, name: &str) -> String {
+        format!("{}_{:#x}", name, self.get_hash())
+    }
+
+    pub fn get_makestring_name(&self) -> String {
+        self.uniquify_name_per_mod("autocxx_make_string")
+    }
+
+    pub fn is_rust_type(&self, id: &Ident) -> bool {
+        self.rust_types
+            .iter()
+            .any(|rt| rt.get_final_ident() == &id.to_string())
+            || self.is_subclass_holder(&id.to_string())
+    }
+
+    fn is_rust_fun(&self, possible_fun: &str) -> bool {
+        self.extern_rust_funs
+            .iter()
+            .map(|fun| &fun.sig.ident)
+            .any(|id| id == possible_fun)
+    }
+
+    pub fn superclasses(&self) -> impl Iterator<Item = &String> {
+        let mut uniquified = HashSet::new();
+        uniquified.extend(self.subclasses.iter().map(|sc| &sc.superclass));
+        uniquified.into_iter()
+    }
+
+    pub fn is_subclass_holder(&self, id: &str) -> bool {
+        self.subclasses
+            .iter()
+            .any(|sc| format!("{}Holder", sc.subclass) == id)
+    }
+
+    fn is_subclass_cpp(&self, id: &str) -> bool {
+        self.subclasses
+            .iter()
+            .any(|sc| format!("{}Cpp", sc.subclass) == id)
+    }
+
+    /// Return the filename to which generated .rs should be written.
+    pub fn get_rs_filename(&self) -> String {
+        format!(
+            "autocxx-{}-gen.rs",
+            self.mod_name
+                .as_ref()
+                .map(|id| id.to_string())
+                .unwrap_or_else(|| "ffi-default".into())
+        )
+    }
+
+    pub fn confirm_complete(&mut self) {
+        if matches!(self.allowlist, Allowlist::Unspecified(_)) {
+            self.allowlist = Allowlist::Specific(Vec::new());
+        }
+    }
+
+    /// Used in reduction to substitute all included headers with a single
+    /// preprocessed replacement.
+    pub fn replace_included_headers(&mut self, replacement: &str) {
+        self.inclusions.clear();
+        self.inclusions.push(replacement.to_string());
+    }
+}
+
+#[cfg(feature = "reproduction_case")]
+impl ToTokens for IncludeCppConfig {
+    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+        let directives = get_directives();
+        let hexathorpe = syn::token::Pound(Span::call_site());
+        for (id, directive) in &directives.need_hexathorpe {
+            let id = format_ident!("{}", id);
+            for output in directive.output(self) {
+                tokens.extend(quote! {
+                    #hexathorpe #id #output
+                })
+            }
+        }
+        for (id, directive) in &directives.need_exclamation {
+            let id = format_ident!("{}", id);
+            for output in directive.output(self) {
+                tokens.extend(quote! {
+                    #id ! (#output)
+                })
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod parse_tests {
+    use crate::config::UnsafePolicy;
+    use syn::parse_quote;
+    #[test]
+    fn test_safety_unsafe() {
+        let us: UnsafePolicy = parse_quote! {
+            unsafe
+        };
+        assert_eq!(us, UnsafePolicy::AllFunctionsSafe)
+    }
+
+    #[test]
+    fn test_safety_unsafe_ffi() {
+        let us: UnsafePolicy = parse_quote! {
+            unsafe_ffi
+        };
+        assert_eq!(us, UnsafePolicy::AllFunctionsSafe)
+    }
+
+    #[test]
+    fn test_safety_safe() {
+        let us: UnsafePolicy = parse_quote! {};
+        assert_eq!(us, UnsafePolicy::AllFunctionsUnsafe)
+    }
+}
diff --git a/parser/src/directives.rs b/parser/src/directives.rs
new file mode 100644
index 0000000..160829e
--- /dev/null
+++ b/parser/src/directives.rs
@@ -0,0 +1,540 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use once_cell::sync::OnceCell;
+use proc_macro2::Span;
+use proc_macro2::{Ident, TokenStream};
+use quote::{quote, ToTokens};
+use syn::parse::ParseStream;
+
+use crate::config::{Allowlist, AllowlistErr};
+use crate::directive_names::{EXTERN_RUST_FUN, EXTERN_RUST_TYPE, SUBCLASS};
+use crate::{AllowlistEntry, IncludeCppConfig};
+use crate::{ParseResult, RustFun, RustPath};
+
+pub(crate) struct DirectivesMap {
+    pub(crate) need_hexathorpe: HashMap<String, Box<dyn Directive>>,
+    pub(crate) need_exclamation: HashMap<String, Box<dyn Directive>>,
+}
+
+static DIRECTIVES: OnceCell<DirectivesMap> = OnceCell::new();
+
+pub(crate) fn get_directives() -> &'static DirectivesMap {
+    DIRECTIVES.get_or_init(|| {
+        let mut need_hexathorpe: HashMap<String, Box<dyn Directive>> = HashMap::new();
+        need_hexathorpe.insert("include".into(), Box::new(Inclusion));
+        let mut need_exclamation: HashMap<String, Box<dyn Directive>> = HashMap::new();
+        need_exclamation.insert("generate".into(), Box::new(Generate(false)));
+        need_exclamation.insert("generate_pod".into(), Box::new(Generate(true)));
+        need_exclamation.insert("generate_ns".into(), Box::new(GenerateNs));
+        need_exclamation.insert("generate_all".into(), Box::new(GenerateAll));
+        need_exclamation.insert("safety".into(), Box::new(Safety));
+        need_exclamation.insert(
+            "pod".into(),
+            Box::new(StringList(
+                |config| &mut config.pod_requests,
+                |config| &config.pod_requests,
+            )),
+        );
+        need_exclamation.insert(
+            "block".into(),
+            Box::new(StringList(
+                |config| &mut config.blocklist,
+                |config| &config.blocklist,
+            )),
+        );
+        need_exclamation.insert(
+            "block_constructors".into(),
+            Box::new(StringList(
+                |config| &mut config.constructor_blocklist,
+                |config| &config.constructor_blocklist,
+            )),
+        );
+        need_exclamation.insert(
+            "instantiable".into(),
+            Box::new(StringList(
+                |config| &mut config.instantiable,
+                |config| &config.instantiable,
+            )),
+        );
+        need_exclamation.insert(
+            "parse_only".into(),
+            Box::new(BoolFlag(
+                |config| &mut config.parse_only,
+                |config| &config.parse_only,
+            )),
+        );
+        need_exclamation.insert(
+            "exclude_impls".into(),
+            Box::new(BoolFlag(
+                |config| &mut config.exclude_impls,
+                |config| &config.exclude_impls,
+            )),
+        );
+        need_exclamation.insert(
+            "exclude_utilities".into(),
+            Box::new(BoolFlag(
+                |config| &mut config.exclude_utilities,
+                |config| &config.exclude_utilities,
+            )),
+        );
+        need_exclamation.insert("name".into(), Box::new(ModName));
+        need_exclamation.insert("concrete".into(), Box::new(Concrete));
+        need_exclamation.insert("rust_type".into(), Box::new(RustType { output: false }));
+        need_exclamation.insert(EXTERN_RUST_TYPE.into(), Box::new(RustType { output: true }));
+        need_exclamation.insert(SUBCLASS.into(), Box::new(Subclass));
+        need_exclamation.insert(EXTERN_RUST_FUN.into(), Box::new(ExternRustFun));
+        need_exclamation.insert(
+            "extern_cpp_type".into(),
+            Box::new(ExternCppType { opaque: false }),
+        );
+        need_exclamation.insert(
+            "extern_cpp_opaque_type".into(),
+            Box::new(ExternCppType { opaque: true }),
+        );
+
+        DirectivesMap {
+            need_hexathorpe,
+            need_exclamation,
+        }
+    })
+}
+
+/// Trait for handling an `include_cpp!` configuration directive.
+pub(crate) trait Directive: Send + Sync {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        span: &Span,
+    ) -> ParseResult<()>;
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a>;
+}
+
+struct Inclusion;
+
+impl Directive for Inclusion {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _span: &Span,
+    ) -> ParseResult<()> {
+        let hdr: syn::LitStr = args.parse()?;
+        config.inclusions.push(hdr.value());
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        Box::new(config.inclusions.iter().map(|val| quote! { #val }))
+    }
+}
+
+/// Directive for either `generate!` (false) or `generate_pod!` (true).
+struct Generate(bool);
+
+impl Directive for Generate {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        span: &Span,
+    ) -> ParseResult<()> {
+        let generate: syn::LitStr = args.parse()?;
+        config
+            .allowlist
+            .push(AllowlistEntry::Item(generate.value()))
+            .map_err(|e| allowlist_err_to_syn_err(e, span))?;
+        if self.0 {
+            config.pod_requests.push(generate.value());
+        }
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        match &config.allowlist {
+            Allowlist::Specific(items) if !self.0 => Box::new(
+                items
+                    .iter()
+                    .flat_map(|i| match i {
+                        AllowlistEntry::Item(s) => Some(s),
+                        _ => None,
+                    })
+                    .map(|s| quote! { #s }),
+            ),
+            Allowlist::Unspecified(_) => panic!("Allowlist mode not yet determined"),
+            _ => Box::new(std::iter::empty()),
+        }
+    }
+}
+
+struct GenerateNs;
+
+impl Directive for GenerateNs {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        span: &Span,
+    ) -> ParseResult<()> {
+        let generate: syn::LitStr = args.parse()?;
+        config
+            .allowlist
+            .push(AllowlistEntry::Namespace(generate.value()))
+            .map_err(|e| allowlist_err_to_syn_err(e, span))?;
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        match &config.allowlist {
+            Allowlist::Specific(items) => Box::new(
+                items
+                    .iter()
+                    .flat_map(|i| match i {
+                        AllowlistEntry::Namespace(s) => Some(s),
+                        _ => None,
+                    })
+                    .map(|s| quote! { #s }),
+            ),
+            Allowlist::Unspecified(_) => panic!("Allowlist mode not yet determined"),
+            _ => Box::new(std::iter::empty()),
+        }
+    }
+}
+
+struct GenerateAll;
+
+impl Directive for GenerateAll {
+    fn parse(
+        &self,
+        _args: ParseStream,
+        config: &mut IncludeCppConfig,
+        span: &Span,
+    ) -> ParseResult<()> {
+        config
+            .allowlist
+            .set_all()
+            .map_err(|e| allowlist_err_to_syn_err(e, span))?;
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        match &config.allowlist {
+            Allowlist::All => Box::new(std::iter::once(TokenStream::new())),
+            Allowlist::Unspecified(_) => panic!("Allowlist mode not yet determined"),
+            _ => Box::new(std::iter::empty()),
+        }
+    }
+}
+
+struct Safety;
+
+impl Directive for Safety {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        config.unsafe_policy = args.parse()?;
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        let policy = &config.unsafe_policy;
+        match config.unsafe_policy {
+            crate::UnsafePolicy::AllFunctionsSafe => {
+                Box::new(std::iter::once(policy.to_token_stream()))
+            }
+            crate::UnsafePolicy::AllFunctionsUnsafe => Box::new(std::iter::empty()),
+        }
+    }
+}
+
+fn allowlist_err_to_syn_err(err: AllowlistErr, span: &Span) -> syn::Error {
+    syn::Error::new(*span, format!("{}", err))
+}
+
+struct StringList<SET, GET>(SET, GET)
+where
+    SET: Fn(&mut IncludeCppConfig) -> &mut Vec<String>,
+    GET: Fn(&IncludeCppConfig) -> &Vec<String>;
+
+impl<SET, GET> Directive for StringList<SET, GET>
+where
+    SET: Fn(&mut IncludeCppConfig) -> &mut Vec<String> + Sync + Send,
+    GET: Fn(&IncludeCppConfig) -> &Vec<String> + Sync + Send,
+{
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        let val: syn::LitStr = args.parse()?;
+        self.0(config).push(val.value());
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        Box::new(self.1(config).iter().map(|val| {
+            quote! {
+                #val
+            }
+        }))
+    }
+}
+
+struct BoolFlag<SET, GET>(SET, GET)
+where
+    SET: Fn(&mut IncludeCppConfig) -> &mut bool,
+    GET: Fn(&IncludeCppConfig) -> &bool;
+
+impl<SET, GET> Directive for BoolFlag<SET, GET>
+where
+    SET: Fn(&mut IncludeCppConfig) -> &mut bool + Sync + Send,
+    GET: Fn(&IncludeCppConfig) -> &bool + Sync + Send,
+{
+    fn parse(
+        &self,
+        _args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        *self.0(config) = true;
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        if *self.1(config) {
+            Box::new(std::iter::once(quote! {}))
+        } else {
+            Box::new(std::iter::empty())
+        }
+    }
+}
+
+struct ModName;
+
+impl Directive for ModName {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        let id: Ident = args.parse()?;
+        config.mod_name = Some(id);
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        match &config.mod_name {
+            None => Box::new(std::iter::empty()),
+            Some(id) => Box::new(std::iter::once(quote! { #id })),
+        }
+    }
+}
+
+struct Concrete;
+
+impl Directive for Concrete {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        let definition: syn::LitStr = args.parse()?;
+        args.parse::<syn::token::Comma>()?;
+        let rust_id: syn::Ident = args.parse()?;
+        config.concretes.0.insert(definition.value(), rust_id);
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        Box::new(config.concretes.0.iter().map(|(k, v)| {
+            quote! {
+                #k,#v
+            }
+        }))
+    }
+}
+
+struct RustType {
+    output: bool,
+}
+
+impl Directive for RustType {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        let id: Ident = args.parse()?;
+        config.rust_types.push(RustPath::new_from_ident(id));
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        if self.output {
+            Box::new(config.rust_types.iter().map(|rp| rp.to_token_stream()))
+        } else {
+            Box::new(std::iter::empty())
+        }
+    }
+}
+
+struct Subclass;
+
+impl Directive for Subclass {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        let superclass: syn::LitStr = args.parse()?;
+        args.parse::<syn::token::Comma>()?;
+        let subclass: syn::Ident = args.parse()?;
+        config.subclasses.push(crate::config::Subclass {
+            superclass: superclass.value(),
+            subclass,
+        });
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        Box::new(config.subclasses.iter().map(|sc| {
+            let superclass = &sc.superclass;
+            let subclass = &sc.subclass;
+            quote! {
+                #superclass,#subclass
+            }
+        }))
+    }
+}
+
+struct ExternRustFun;
+
+impl Directive for ExternRustFun {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        let path: RustPath = args.parse()?;
+        args.parse::<syn::token::Comma>()?;
+        let sig: syn::Signature = args.parse()?;
+        config.extern_rust_funs.push(RustFun {
+            path,
+            sig,
+            receiver: None,
+        });
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        Box::new(config.extern_rust_funs.iter().map(|erf| {
+            let p = &erf.path;
+            let s = &erf.sig;
+            quote! { #p,#s }
+        }))
+    }
+}
+
+struct ExternCppType {
+    opaque: bool,
+}
+
+impl Directive for ExternCppType {
+    fn parse(
+        &self,
+        args: ParseStream,
+        config: &mut IncludeCppConfig,
+        _ident_span: &Span,
+    ) -> ParseResult<()> {
+        let definition: syn::LitStr = args.parse()?;
+        args.parse::<syn::token::Comma>()?;
+        let rust_path: syn::TypePath = args.parse()?;
+        config.externs.0.insert(
+            definition.value(),
+            crate::config::ExternCppType {
+                rust_path,
+                opaque: self.opaque,
+            },
+        );
+        Ok(())
+    }
+
+    fn output<'a>(
+        &self,
+        config: &'a IncludeCppConfig,
+    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
+        let opaque_needed = self.opaque;
+        Box::new(
+            config
+                .externs
+                .0
+                .iter()
+                .filter_map(move |(definition, details)| {
+                    if details.opaque == opaque_needed {
+                        let rust_path = &details.rust_path;
+                        Some(quote! {
+                            #definition, #rust_path
+                        })
+                    } else {
+                        None
+                    }
+                }),
+        )
+    }
+}
diff --git a/parser/src/file_locations.rs b/parser/src/file_locations.rs
new file mode 100644
index 0000000..a113d0f
--- /dev/null
+++ b/parser/src/file_locations.rs
@@ -0,0 +1,159 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use proc_macro2::TokenStream;
+use quote::quote;
+use std::{fs::File, path::PathBuf};
+
+use crate::{multi_bindings::MultiBindings, IncludeCppConfig};
+
+/// The strategy used to generate, and to find generated, files.
+/// As standard, these are based off the OUT_DIR set by Cargo,
+/// but our code can't assume it can read OUT_DIR as it may
+/// be running in the rust-analyzer proc macro server where it's
+/// not available. We need to give provision for custom locations
+/// and we need our code generator build script to be able to pass
+/// locations through to the proc macro by env vars.
+///
+/// On the whole this class concerns itself with directory names
+/// and allows the actual file name to be determined elsewhere
+/// (based on a hash of the contents of `include_cpp!`.) But
+/// some types of build system need to know the precise file _name_
+/// produced by the codegen phase and passed into the macro phase,
+/// so we have some options for that. See `gen --help` for details.
+pub enum FileLocationStrategy {
+    Custom(PathBuf),
+    FromAutocxxRsFile(PathBuf),
+    FromAutocxxRs(PathBuf),
+    FromOutDir(PathBuf),
+    FromAutocxxRsJsonArchive(PathBuf),
+    UnknownMaybeFromOutdir,
+}
+
+static BUILD_DIR_NAME: &str = "autocxx-build-dir";
+static RS_DIR_NAME: &str = "rs";
+static AUTOCXX_RS: &str = "AUTOCXX_RS";
+static AUTOCXX_RS_FILE: &str = "AUTOCXX_RS_FILE";
+static AUTOCXX_RS_JSON_ARCHIVE: &str = "AUTOCXX_RS_JSON_ARCHIVE";
+
+impl FileLocationStrategy {
+    pub fn new() -> Self {
+        match std::env::var_os(AUTOCXX_RS_JSON_ARCHIVE) {
+            Some(of) => FileLocationStrategy::FromAutocxxRsJsonArchive(PathBuf::from(of)),
+            None => match std::env::var_os(AUTOCXX_RS_FILE) {
+                Some(of) => FileLocationStrategy::FromAutocxxRsFile(PathBuf::from(of)),
+                None => match std::env::var_os(AUTOCXX_RS) {
+                    None => match std::env::var_os("OUT_DIR") {
+                        None => FileLocationStrategy::UnknownMaybeFromOutdir,
+                        Some(od) => FileLocationStrategy::FromOutDir(PathBuf::from(od)),
+                    },
+                    Some(acrs) => FileLocationStrategy::FromAutocxxRs(PathBuf::from(acrs)),
+                },
+            },
+        }
+    }
+
+    pub fn new_custom(gen_dir: PathBuf) -> Self {
+        FileLocationStrategy::Custom(gen_dir)
+    }
+
+    /// Make a macro to include a given generated Rust file name.
+    /// This can't simply be calculated from `get_rs_dir` because
+    /// of limitations in rust-analyzer.
+    pub fn make_include(&self, config: &IncludeCppConfig) -> TokenStream {
+        match self {
+            FileLocationStrategy::FromAutocxxRs(custom_dir) => {
+                let fname = config.get_rs_filename();
+                let fname = custom_dir.join(fname).to_str().unwrap().to_string();
+                quote! {
+                    include!( #fname );
+                }
+            }
+            FileLocationStrategy::Custom(_) => panic!("Should never happen in the macro"),
+            FileLocationStrategy::UnknownMaybeFromOutdir | FileLocationStrategy::FromOutDir(_) => {
+                let fname = config.get_rs_filename();
+                let fname = format!("/{}/{}/{}", BUILD_DIR_NAME, RS_DIR_NAME, fname);
+                // rust-analyzer works better if we ask Rust to do the path
+                // concatenation rather than doing it in proc-macro code.
+                // proc-macro code does not itself have access to the value of
+                // OUT_DIR, but if we put it into a macro like the below,
+                // rust-analyzer can cope.
+                quote! {
+                    include!(concat!(env!("OUT_DIR"), #fname));
+                }
+            }
+            FileLocationStrategy::FromAutocxxRsFile(fname) => {
+                let fname = fname
+                    .to_str()
+                    .expect("AUTOCXX_RS_FILE environment variable contained non-UTF8 characters");
+                quote! {
+                    include!( #fname );
+                }
+            }
+            FileLocationStrategy::FromAutocxxRsJsonArchive(fname) => {
+                let archive = File::open(fname).unwrap_or_else(|_| panic!("Unable to open {}. This may mean you didn't run the codegen tool (autocxx_gen) before building the Rust code.", fname.to_string_lossy()));
+                let multi_bindings: MultiBindings = serde_json::from_reader(archive)
+                    .unwrap_or_else(|_| {
+                        panic!("Unable to interpret {} as JSON", fname.to_string_lossy())
+                    });
+                multi_bindings.get(config).unwrap_or_else(|err| panic!("Unable to find a suitable set of bindings within the JSON archive {} ({}). This likely means that the codegen tool hasn't been rerun since some changes in your include_cpp! macro.", fname.to_string_lossy(), err))
+            }
+        }
+    }
+
+    fn get_gen_dir(&self, suffix: &str) -> PathBuf {
+        let root = match self {
+            FileLocationStrategy::Custom(gen_dir)
+            | FileLocationStrategy::FromAutocxxRs(gen_dir) => gen_dir.clone(),
+            FileLocationStrategy::FromOutDir(out_dir) => out_dir.join(BUILD_DIR_NAME),
+            FileLocationStrategy::UnknownMaybeFromOutdir => {
+                panic!("Could not determine OUT_DIR or AUTOCXX_RS dir")
+            }
+            FileLocationStrategy::FromAutocxxRsFile(_) => {
+                panic!("It's invalid to set AUTOCXX_RS_FILE during the codegen phase.")
+            }
+            FileLocationStrategy::FromAutocxxRsJsonArchive(_) => {
+                panic!("It's invalid to set AUTOCXX_RS_JSON_ARCHIVE during the codegen phase.")
+            }
+        };
+        root.join(suffix)
+    }
+
+    /// Location to generate Rust files.
+    pub fn get_rs_dir(&self) -> PathBuf {
+        self.get_gen_dir(RS_DIR_NAME)
+    }
+
+    /// Location to generate C++ header files.
+    pub fn get_include_dir(&self) -> PathBuf {
+        self.get_gen_dir("include")
+    }
+
+    /// Location to generate C++ code.
+    pub fn get_cxx_dir(&self) -> PathBuf {
+        self.get_gen_dir("cxx")
+    }
+
+    /// From a build script, inform cargo how to set environment variables
+    /// to make them available to the procedural macro.
+    pub fn set_cargo_env_vars_for_build(&self) {
+        if let FileLocationStrategy::Custom(_) = self {
+            println!(
+                "cargo:rustc-env={}={}",
+                AUTOCXX_RS,
+                self.get_rs_dir().to_str().unwrap()
+            );
+        }
+    }
+}
+
+impl Default for FileLocationStrategy {
+    fn default() -> Self {
+        Self::new()
+    }
+}
diff --git a/parser/src/lib.rs b/parser/src/lib.rs
new file mode 100644
index 0000000..9e3ba7e
--- /dev/null
+++ b/parser/src/lib.rs
@@ -0,0 +1,83 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![forbid(unsafe_code)]
+
+mod config;
+mod directives;
+pub mod file_locations;
+mod multi_bindings;
+mod path;
+mod subclass_attrs;
+
+pub use config::{
+    AllowlistEntry, ExternCppType, IncludeCppConfig, RustFun, Subclass, UnsafePolicy,
+};
+use file_locations::FileLocationStrategy;
+pub use multi_bindings::{MultiBindings, MultiBindingsErr};
+pub use path::RustPath;
+use proc_macro2::TokenStream as TokenStream2;
+pub use subclass_attrs::SubclassAttrs;
+use syn::Result as ParseResult;
+use syn::{
+    parse::{Parse, ParseStream},
+    Macro,
+};
+
+#[doc(hidden)]
+/// Ensure consistency between the `include_cpp!` parser
+/// and the standalone macro discoverer
+pub mod directive_names {
+    pub static EXTERN_RUST_TYPE: &str = "extern_rust_type";
+    pub static EXTERN_RUST_FUN: &str = "extern_rust_function";
+    pub static SUBCLASS: &str = "subclass";
+}
+
+/// Core of the autocxx engine. See `generate` for most details
+/// on how this works.
+pub struct IncludeCpp {
+    config: IncludeCppConfig,
+}
+
+impl Parse for IncludeCpp {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        let config = input.parse::<IncludeCppConfig>()?;
+        Ok(Self { config })
+    }
+}
+
+impl IncludeCpp {
+    pub fn new_from_syn(mac: Macro) -> ParseResult<Self> {
+        mac.parse_body::<IncludeCpp>()
+    }
+
+    /// Generate the Rust bindings.
+    pub fn generate_rs(&self) -> TokenStream2 {
+        if self.config.parse_only {
+            return TokenStream2::new();
+        }
+        FileLocationStrategy::new().make_include(&self.config)
+    }
+
+    pub fn get_config(&self) -> &IncludeCppConfig {
+        &self.config
+    }
+}
+
+#[cfg(test)]
+mod parse_tests {
+    use crate::IncludeCpp;
+    use syn::parse_quote;
+
+    #[test]
+    fn test_basic() {
+        let _i: IncludeCpp = parse_quote! {
+            generate_all!()
+        };
+    }
+}
diff --git a/parser/src/multi_bindings.rs b/parser/src/multi_bindings.rs
new file mode 100644
index 0000000..4ca86d9
--- /dev/null
+++ b/parser/src/multi_bindings.rs
@@ -0,0 +1,93 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::IndexMap;
+use proc_macro2::TokenStream;
+use serde::{Deserialize, Serialize};
+
+use crate::IncludeCppConfig;
+
+/// Struct which stores multiple sets of bindings and can be serialized
+/// to disk. This is used when our build system uses `autocxx_gen`; that
+/// can handle multiple `include_cpp!` macros and therefore generate multiple
+/// sets of Rust bindings. We can't simply `include!` those because there's
+/// no (easy) way to pass their details from the codegen phase across to
+/// the Rust macro phase. Instead, we use this data structure to store
+/// several sets of .rs bindings in a single file, and then the macro
+/// extracts the correct set of bindings at expansion time.
+#[derive(Serialize, Deserialize, Default)]
+pub struct MultiBindings(IndexMap<u64, String>);
+
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum MultiBindingsErr {
+    #[error("unable to find the desired bindings within the archive of Rust bindings produced by the autocxx code generation phase")]
+    MissingBindings,
+    #[error("the stored bindings within the JSON file could not be parsed as valid Rust tokens")]
+    BindingsNotParseable,
+}
+
+impl MultiBindings {
+    /// Insert some generated Rust bindings into this data structure.
+    pub fn insert(&mut self, config: &IncludeCppConfig, bindings: TokenStream) {
+        self.0.insert(config.get_hash(), bindings.to_string());
+    }
+
+    /// Retrieves the bindings corresponding to a given [`IncludeCppConfig`].
+    pub fn get(&self, config: &IncludeCppConfig) -> Result<TokenStream, MultiBindingsErr> {
+        match self.0.get(&(config.get_hash())) {
+            None => Err(MultiBindingsErr::MissingBindings),
+            Some(bindings) => Ok(bindings
+                .parse()
+                .map_err(|_| MultiBindingsErr::BindingsNotParseable)?),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use proc_macro2::Span;
+    use quote::quote;
+    use syn::parse_quote;
+
+    use crate::IncludeCppConfig;
+
+    use super::MultiBindings;
+
+    #[test]
+    fn test_multi_bindings() {
+        let hexathorpe = syn::token::Pound(Span::call_site());
+        let config1: IncludeCppConfig = parse_quote! {
+            #hexathorpe include "a.h"
+            generate!("Foo")
+        };
+        let config2: IncludeCppConfig = parse_quote! {
+            #hexathorpe include "b.h"
+            generate!("Bar")
+        };
+        let config3: IncludeCppConfig = parse_quote! {
+            #hexathorpe include "c.h"
+            generate!("Bar")
+        };
+        let mut multi_bindings = MultiBindings::default();
+        multi_bindings.insert(&config1, quote! { first; });
+        multi_bindings.insert(&config2, quote! { second; });
+        let json = serde_json::to_string(&multi_bindings).unwrap();
+        let multi_bindings2: MultiBindings = serde_json::from_str(&json).unwrap();
+        assert_eq!(
+            multi_bindings2.get(&config2).unwrap().to_string(),
+            "second ;"
+        );
+        assert_eq!(
+            multi_bindings2.get(&config1).unwrap().to_string(),
+            "first ;"
+        );
+        assert!(multi_bindings2.get(&config3).is_err());
+    }
+}
diff --git a/parser/src/path.rs b/parser/src/path.rs
new file mode 100644
index 0000000..415b2ef
--- /dev/null
+++ b/parser/src/path.rs
@@ -0,0 +1,67 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::ParseResult;
+use proc_macro2::Ident;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::parse::{Parse, ParseStream};
+
+/// A little like [`syn::Path`] but simpler - contains only identifiers,
+/// no path arguments. Guaranteed to always have at least one identifier.
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct RustPath(Vec<Ident>);
+
+impl RustPath {
+    pub fn new_from_ident(id: Ident) -> Self {
+        Self(vec![id])
+    }
+
+    #[must_use]
+    pub fn append(&self, id: Ident) -> Self {
+        Self(self.0.iter().cloned().chain(std::iter::once(id)).collect())
+    }
+
+    pub fn get_final_ident(&self) -> &Ident {
+        self.0.last().unwrap()
+    }
+
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
+
+impl ToTokens for RustPath {
+    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+        let mut it = self.0.iter();
+        let mut id = it.next();
+        while id.is_some() {
+            id.unwrap().to_tokens(tokens);
+            let next = it.next();
+            if next.is_some() {
+                tokens.append_all(quote! { :: });
+            }
+            id = next;
+        }
+    }
+}
+
+impl Parse for RustPath {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        let id: Ident = input.parse()?;
+        let mut p = RustPath::new_from_ident(id);
+        while input.parse::<Option<syn::token::Colon2>>()?.is_some() {
+            let id: Ident = input.parse()?;
+            p = p.append(id);
+        }
+        Ok(p)
+    }
+}
diff --git a/parser/src/subclass_attrs.rs b/parser/src/subclass_attrs.rs
new file mode 100644
index 0000000..9174bec
--- /dev/null
+++ b/parser/src/subclass_attrs.rs
@@ -0,0 +1,58 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use proc_macro2::Ident;
+use quote::ToTokens;
+use syn::Result as ParseResult;
+use syn::{
+    parse::{Parse, ParseStream},
+    token::Comma,
+};
+
+#[derive(Default)]
+pub struct SubclassAttrs {
+    pub self_owned: bool,
+    pub superclass: Option<String>,
+}
+
+impl Parse for SubclassAttrs {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        let mut me = Self::default();
+        let mut id = input.parse::<Option<Ident>>()?;
+        while id.is_some() {
+            match id {
+                Some(id) if id == "self_owned" => me.self_owned = true,
+                Some(id) if id == "superclass" => {
+                    let args;
+                    syn::parenthesized!(args in input);
+                    let superclass: syn::LitStr = args.parse()?;
+                    if me.superclass.is_some() {
+                        return Err(syn::Error::new_spanned(
+                            id.into_token_stream(),
+                            "Expected single superclass specification",
+                        ));
+                    }
+                    me.superclass = Some(superclass.value());
+                }
+                Some(id) => {
+                    return Err(syn::Error::new_spanned(
+                        id.into_token_stream(),
+                        "Expected self_owned or superclass",
+                    ))
+                }
+                None => {}
+            };
+            let comma = input.parse::<Option<Comma>>()?;
+            if comma.is_none() {
+                break;
+            }
+            id = input.parse::<Option<Ident>>()?;
+        }
+        Ok(me)
+    }
+}