use indexmap::map::IndexMap as HashMap;
use once_cell::sync::OnceCell;
use proc_macro2::Ident;
use proc_macro2::Span;
#[cfg(feature = "reproduction_case")]
use proc_macro2::TokenStream;
#[cfg(feature = "reproduction_case")]
use quote::{quote, ToTokens};
use syn::parse::ParseStream;
use crate::config::AllowlistErr;
#[cfg(feature = "reproduction_case")]
use crate::config::Allowlist;
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,
}
})
}
pub(crate) trait Directive: Send + Sync {
fn parse(
&self,
args: ParseStream,
config: &mut IncludeCppConfig,
span: &Span,
) -> ParseResult<()>;
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
fn output<'a>(
&self,
config: &'a IncludeCppConfig,
) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
Box::new(config.inclusions.iter().map(|val| quote! { #val }))
}
}
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(())
}
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
fn output<'a>(
&self,
config: &'a IncludeCppConfig,
) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
let policy = &config.unsafe_policy;
match config.unsafe_policy {
crate::UnsafePolicy::AllFunctionsUnsafe => Box::new(std::iter::empty()),
_ => Box::new(std::iter::once(policy.to_token_stream())),
}
}
}
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(())
}
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
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 {
#[allow(dead_code)]
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(())
}
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
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,
has_receiver: false,
});
Ok(())
}
#[cfg(feature = "reproduction_case")]
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(())
}
#[cfg(feature = "reproduction_case")]
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
}
}),
)
}
}