use super::prelude::*;
use OptionDetails as OD;
#[derive(Debug, Copy, Clone)]
pub enum OpContext {
Template,
DriverApplicationCapture,
DriverApplicationPassed,
}
#[derive(Default, Debug, Clone)]
pub struct UnprocessedOptions(TokenStream);
#[derive(Default, Debug, Clone)]
pub struct DaOptions {
pub dbg: bool,
pub driver_kind: Option<DaOptVal<ExpectedDriverKind>>,
pub expect_target: Option<DaOptVal<check::Target>>,
}
#[derive(Debug)]
struct DaOption {
#[allow(dead_code)] pub kw_span: Span,
pub od: OptionDetails,
}
#[derive(Debug, Clone)]
#[allow(non_camel_case_types)] enum OptionDetails {
dbg,
For(DaOptVal<ExpectedDriverKind>),
expect(DaOptVal<check::Target>),
}
#[derive(Debug, Clone, Copy)]
pub struct DaOptVal<V> {
pub value: V,
pub span: Span,
}
pub trait DaOptValDescribable {
const DESCRIPTION: &'static str;
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, EnumString, Display)]
#[strum(serialize_all = "snake_case")]
pub enum ExpectedDriverKind {
Struct,
Enum,
Union,
}
#[derive(Debug, Copy, Clone)]
pub struct OpCompatVersions {
major: OpCompatVersionNumber,
minor: OpCompatVersionNumber,
span: Span,
}
type OpCompatVersionNumber = u32;
impl OpCompatVersions {
pub fn ours() -> Self {
OpCompatVersions {
major: 1,
minor: 0,
span: Span::call_site(),
}
}
}
impl DaOptValDescribable for ExpectedDriverKind {
const DESCRIPTION: &'static str = "expected driver kind (in `for` option)";
}
impl OpContext {
fn allowed(self, option: &DaOption) -> syn::Result<()> {
use OpContext as OC;
match &option.od {
OD::dbg | OD::expect(..) => return Ok(()),
OD::For(..) => {}
}
match self {
OC::Template => Ok(()),
OC::DriverApplicationCapture | OC::DriverApplicationPassed => {
Err(option.kw_span.error(
"this derive-adhoc option is only supported in templates",
))
}
}
}
fn parse_versions(
self,
input: ParseStream,
) -> syn::Result<OpCompatVersions> {
use OpContext as OC;
let ours = OpCompatVersions::ours();
let got = match self {
OC::Template | OC::DriverApplicationCapture => ours,
OC::DriverApplicationPassed => input.parse()?,
};
if got.major != ours.major {
return Err(got.error(format_args!(
"Incompatible major version for AOPTIONS (driver {}, template/engine {})",
got.major,
ours.major,
)));
}
Ok(got)
}
}
impl ToTokens for OpCompatVersions {
fn to_tokens(&self, out: &mut TokenStream) {
let OpCompatVersions { major, minor, span } = OpCompatVersions::ours();
out.extend(quote_spanned! {span=> #major #minor });
}
}
impl Parse for OpCompatVersions {
fn parse(input: ParseStream) -> syn::Result<Self> {
let number = move || {
let lit: syn::LitInt = input.parse()?;
Ok::<_, syn::Error>((lit.span(), lit.base10_parse()?))
};
let (span, major) = number()?;
let (_, minor) = number()?;
Ok(OpCompatVersions { major, minor, span })
}
}
fn continue_options(input: ParseStream) -> Option<Lookahead1> {
if input.is_empty() {
return None;
}
let la = input.lookahead1();
if la.peek(Token![:]) || la.peek(Token![=]) {
return None;
}
Some(la)
}
impl UnprocessedOptions {
pub fn parse(
input: ParseStream,
opcontext: OpContext,
) -> syn::Result<Self> {
DaOption::parse_several(&input.fork(), opcontext, |_| Ok(()))?;
let mut out = TokenStream::new();
while continue_options(input).is_some() {
let tt: TokenTree = input.parse()?;
out.extend([tt]);
}
Ok(UnprocessedOptions(out))
}
}
impl DaOptions {
pub fn parse_update(
&mut self,
input: ParseStream,
opcontext: OpContext,
) -> syn::Result<()> {
DaOption::parse_several(input, opcontext, |option| {
self.update_from_option(option)
})
}
}
impl DaOption {
fn parse_several(
input: ParseStream,
opcontext: OpContext,
mut each: impl FnMut(DaOption) -> syn::Result<()>,
) -> syn::Result<()> {
let _versions = opcontext
.parse_versions(input)
.map_err(advise_incompatibility)?;
while let Some(la) = continue_options(input) {
if !la.peek(Ident::peek_any) {
return Err(la.error());
}
let option = input.parse()?;
opcontext.allowed(&option)?;
each(option)?;
let la = if let Some(la) = continue_options(input) {
la
} else {
break;
};
if !la.peek(Token![,]) {
return Err(la.error());
}
let _: Token![,] = input.parse()?;
}
Ok(())
}
}
impl Parse for DaOption {
fn parse(input: ParseStream) -> syn::Result<Self> {
let kw: IdentAny = input.parse()?;
let from_od = |od| {
Ok(DaOption {
kw_span: kw.span(),
od,
})
};
macro_rules! keyword { { $($args:tt)* } => {
keyword_general! { kw from_od OD; $($args)* }
} }
keyword! { dbg }
keyword! { "for": For(input.parse()?) }
keyword! { expect(input.parse()?) }
Err(kw.error("unknown derive-adhoc option"))
}
}
impl<V: FromStr + DaOptValDescribable> Parse for DaOptVal<V> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let kw: IdentAny = input.parse()?;
let value = kw.to_string().parse().map_err(|_| {
kw.error(format_args!("unknown value for {}", V::DESCRIPTION))
})?;
let span = kw.span();
Ok(DaOptVal { value, span })
}
}
impl ToTokens for UnprocessedOptions {
fn to_tokens(&self, out: &mut TokenStream) {
out.extend(self.0.clone());
}
}
impl UnprocessedOptions {
#[allow(dead_code)] pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl DaOptions {
fn update_from_option(&mut self, option: DaOption) -> syn::Result<()> {
fn store<V>(
already: &mut Option<DaOptVal<V>>,
new: DaOptVal<V>,
) -> syn::Result<()>
where
V: PartialEq + DaOptValDescribable,
{
match already {
Some(already) if already.value == new.value => Ok(()),
Some(already) => {
Err([(already.span, "first"), (new.span, "second")].error(
format_args!(
"contradictory values for {}",
V::DESCRIPTION,
),
))
}
None => {
*already = Some(new);
Ok(())
}
}
}
Ok(match option.od {
OD::dbg => self.dbg = true,
OD::expect(spec) => store(&mut self.expect_target, spec)?,
OD::For(spec) => store(&mut self.driver_kind, spec)?,
})
}
}