derive_deftly_macros/
options.rs

1//! "Options", keyword modifiers for an expansion
2//!
3//! These "combine": multiple specifications of the same option
4//! are allowed, so long as they are compatible.
5
6use super::prelude::*;
7use adviseable::*;
8
9use OptionDetails as OD;
10
11//---------- types, ordered from general to specific ----------
12
13/// Where are we finding these options?
14#[derive(Debug, Copy, Clone)]
15pub enum OpContext {
16    /// Just before a precanned template
17    ///
18    /// `define_derivae_deftly!{ Template OPTIONS: ...`
19    TemplateDefinition,
20    /// Just before an adhoc template
21    ///
22    /// `derive_deftly_adhoc!{ Driver OPTIONS: ...`
23    TemplateAdhoc,
24    /// In the driver's application
25    ///
26    /// `#[derive_deftly(Template[OPTIONS])]`
27    DriverApplicationCapture,
28    /// Module definition.
29    ///
30    /// `define_derivae_deftly_module!{ Module OPTIONS: ...`
31    ModuleDefinition,
32    /// Driver's application options as they appear in the ultimate expansion
33    ///
34    /// With the versioning, so `1 1 AOPTIONS`.
35    DriverApplicationPassed,
36}
37
38/// All the template options, as a tokenstream, but sanity-checked
39///
40/// These have been syntax checked, but not semantically checked.
41/// The purpose of the syntax check is to get syntax errors early,
42/// when a template is defined - rather than when it's applied.
43///
44/// This also helps with cross-crate compatibility.
45#[derive(Default, Debug, Clone)]
46pub struct UnprocessedOptions {
47    ts: TokenStream,
48    pub beta_enabled: Option<beta::Enabled>,
49}
50impl_to_tokens!(UnprocessedOptions, ts);
51
52/// Template options, semantically resolved
53#[derive(Default, Debug, Clone)]
54pub struct DdOptions {
55    pub dbg: bool,
56    pub driver_kind: Option<DdOptVal<ExpectedDriverKind>>,
57    pub expect_target: Option<DdOptVal<check::Target>>,
58    pub beta_enabled: Option<beta::Enabled>,
59}
60
61/// A single template option
62#[derive(Debug)]
63struct DdOption {
64    pub kw_span: Span,
65    pub od: OptionDetails,
66}
67
68/// Enum for the details of a template option
69#[derive(Debug, Clone)]
70#[allow(non_camel_case_types)] // clearer to use the exact ident
71enum OptionDetails {
72    dbg,
73    For(DdOptVal<ExpectedDriverKind>),
74    expect(DdOptVal<check::Target>),
75    beta_deftly(beta::Enabled),
76}
77
78/// Value for an option
79///
80/// If `V` is `FromStr` and `DdOptValDescribable`,
81/// this is `Parse`, taking a single keyword.
82#[derive(Debug, Clone, Copy)]
83pub struct DdOptVal<V> {
84    pub value: V,
85    pub span: Span,
86}
87
88/// Things that go into a `DdOptVal`
89pub trait DdOptValDescribable {
90    const DESCRIPTION: &'static str;
91}
92
93/// The (single) expected driver kind
94//
95// At some future point, we may want `for ...` keywords that
96// specify a range of possible drivers.  Then we'll need both
97// this enum, and a *set* of it, and calculate the intersection
98// in update_from_option.
99#[derive(Debug, Clone, Copy, Eq, PartialEq, EnumString, Display)]
100#[strum(serialize_all = "snake_case")]
101pub enum ExpectedDriverKind {
102    Struct,
103    Enum,
104    Union,
105}
106
107#[derive(Debug, Copy, Clone)]
108pub struct OpCompatVersions {
109    /// Increased when we make a wholly-incompatible change
110    ///
111    /// Bumping this will cause rejection of AOPTIONS by old d-d engines.
112    /// Hopefully a newer d-d will able to cope with both.
113    major: OpCompatVersionNumber,
114
115    /// Increased when we make a more subtle change
116    ///
117    /// Current d-d versions will ignore this.
118    /// Bumping it can be used to psas information
119    /// from a newer capturing d-d to a newer template/driver d-d.
120    minor: OpCompatVersionNumber,
121
122    /// Span for error reporting
123    span: Span,
124}
125type OpCompatVersionNumber = u32;
126impl OpCompatVersions {
127    pub fn ours() -> Self {
128        OpCompatVersions {
129            major: 1,
130            minor: 0,
131            span: Span::call_site(),
132        }
133    }
134}
135
136impl DdOptValDescribable for ExpectedDriverKind {
137    const DESCRIPTION: &'static str = "expected driver kind (in `for` option)";
138}
139
140//---------- parsing ----------
141
142impl OpContext {
143    fn allowed(self, option: &DdOption) -> syn::Result<()> {
144        use OpContext as OC;
145        match self {
146            OC::ModuleDefinition => {
147                return match &option.od {
148                    OD::beta_deftly(..) => Ok(()),
149                    _other => Err(option.kw_span.error(
150                        "only beta_deftly is allowed in module definitions",
151                    )),
152                }
153            }
154            _other => {}
155        }
156        match &option.od {
157            OD::dbg => return Ok(()),
158            OD::expect(v) => return check::check_expect_opcontext(v, self),
159            OD::For(..) => {}
160            OD::beta_deftly(..) => {}
161        }
162        match self {
163            OC::TemplateDefinition => Ok(()),
164            OC::TemplateAdhoc => Ok(()),
165            OC::DriverApplicationCapture | OC::DriverApplicationPassed => {
166                Err(option.kw_span.error(
167                    "this derive-deftly option is only supported in templates",
168                ))
169            }
170            OC::ModuleDefinition => panic!("handled earlier"),
171        }
172    }
173
174    fn parse_versions(
175        self,
176        input: ParseStream,
177    ) -> syn::Result<OpCompatVersions> {
178        use OpContext as OC;
179        let ours = OpCompatVersions::ours();
180        let got = match self {
181            OC::TemplateDefinition
182            | OC::TemplateAdhoc
183            | OC::ModuleDefinition
184            | OC::DriverApplicationCapture => ours,
185            OC::DriverApplicationPassed => input.parse()?,
186        };
187        if got.major != ours.major {
188            return Err(got.error(format_args!(
189 "Incompatible major version for AOPTIONS (driver {}, template/engine {})",
190                got.major,
191                ours.major,
192            )));
193        }
194        Ok(got)
195    }
196}
197
198impl ToTokens for OpCompatVersions {
199    fn to_tokens(&self, out: &mut TokenStream) {
200        let OpCompatVersions { major, minor, span } = OpCompatVersions::ours();
201        out.extend(quote_spanned! {span=> #major #minor });
202    }
203}
204
205impl Parse for OpCompatVersions {
206    fn parse(input: ParseStream) -> syn::Result<Self> {
207        let number = move || {
208            let lit: syn::LitInt = input.parse()?;
209            Ok::<_, syn::Error>((lit.span(), lit.base10_parse()?))
210        };
211        let (span, major) = number()?;
212        let (_, minor) = number()?;
213        Ok(OpCompatVersions { major, minor, span })
214    }
215}
216
217fn continue_options(input: ParseStream) -> Option<Lookahead1> {
218    if input.is_empty() {
219        return None;
220    }
221    let la = input.lookahead1();
222    if la.peek(Token![:]) || la.peek(Token![=]) {
223        return None;
224    }
225    Some(la)
226}
227
228impl UnprocessedOptions {
229    pub fn parse(
230        input: ParseStream,
231        opcontext: OpContext,
232    ) -> syn::Result<Self> {
233        let mut beta_enabled = None;
234
235        // Scan ahead for a syntax check (and beta enablement)
236        DdOption::parse_several(&input.fork(), opcontext, |opt| {
237            match &opt.od {
238                OD::beta_deftly(be) => beta_enabled = Some(*be),
239                _ => {}
240            };
241            Ok(())
242        })?;
243
244        // Collect everything until the : or =
245        let mut out = TokenStream::new();
246        while continue_options(input).is_some() {
247            let tt: TokenTree = input.parse()?;
248            out.extend([tt]);
249        }
250        Ok(UnprocessedOptions {
251            ts: out,
252            beta_enabled,
253        })
254    }
255}
256
257impl DdOptions {
258    pub fn parse_update(
259        &mut self,
260        input: ParseStream,
261        opcontext: OpContext,
262    ) -> syn::Result<()> {
263        DdOption::parse_several(input, opcontext, |option| {
264            self.update_from_option(option)
265        })
266    }
267}
268
269impl DdOption {
270    /// Parses zero or more options, in `opcontext`
271    ///
272    /// With `OpContext::DriverApplicationPassed`,
273    /// expects to find the version information too.
274    fn parse_several(
275        input: ParseStream,
276        opcontext: OpContext,
277        mut each: impl FnMut(DdOption) -> syn::Result<()>,
278    ) -> syn::Result<()> {
279        let _versions = opcontext
280            .parse_versions(input)
281            .map_err(advise_incompatibility)?;
282
283        while let Some(la) = continue_options(input) {
284            if !la.peek(Ident::peek_any) {
285                return Err(la.error());
286            }
287            let option = input.parse()?;
288            opcontext.allowed(&option)?;
289            each(option)?;
290
291            let la = if let Some(la) = continue_options(input) {
292                la
293            } else {
294                break;
295            };
296            if !la.peek(Token![,]) {
297                return Err(la.error());
298            }
299            let _: Token![,] = input.parse()?;
300        }
301        Ok(())
302    }
303}
304
305impl Parse for DdOption {
306    fn parse(input: ParseStream) -> syn::Result<Self> {
307        let kw: IdentAny = input.parse()?;
308
309        let from_od = |od| {
310            Ok(DdOption {
311                kw_span: kw.span(),
312                od,
313            })
314        };
315
316        // See keyword_general! in utils.rs
317        macro_rules! keyword { { $($args:tt)* } => {
318            keyword_general! { kw from_od OD; $($args)* }
319        } }
320
321        keyword! { dbg }
322        keyword! { "for": For(input.parse()?) }
323        keyword! { expect(input.parse()?) }
324        keyword! { beta_deftly(beta::Enabled::new_for_dd_option(kw.span())?) }
325
326        Err(kw.error("unknown derive-deftly option"))
327    }
328}
329
330impl<V: FromStr + DdOptValDescribable> Parse for DdOptVal<V> {
331    fn parse(input: ParseStream) -> syn::Result<Self> {
332        let kw: IdentAny = input.parse()?;
333        let value = kw.to_string().parse().map_err(|_| {
334            kw.error(format_args!("unknown value for {}", V::DESCRIPTION))
335        })?;
336        let span = kw.span();
337        Ok(DdOptVal { value, span })
338    }
339}
340
341//---------- processing ----------
342
343impl UnprocessedOptions {
344    #[allow(dead_code)] // Currently unused, retain it in case we need it
345    pub fn is_empty(&self) -> bool {
346        self.ts.is_empty()
347    }
348}
349
350impl DdOptions {
351    /// Update `self` according to the option specified in `option`
352    ///
353    /// On error (eg, contradictory options), fails.
354    fn update_from_option(&mut self, option: DdOption) -> syn::Result<()> {
355        fn store<V>(
356            already: &mut Option<DdOptVal<V>>,
357            new: DdOptVal<V>,
358        ) -> syn::Result<()>
359        where
360            V: PartialEq + DdOptValDescribable,
361        {
362            match already {
363                Some(already) if already.value == new.value => Ok(()),
364                Some(already) => {
365                    Err([(already.span, "first"), (new.span, "second")].error(
366                        format_args!(
367                            "contradictory values for {}",
368                            V::DESCRIPTION,
369                        ),
370                    ))
371                }
372                None => {
373                    *already = Some(new);
374                    Ok(())
375                }
376            }
377        }
378
379        Ok(match option.od {
380            OD::dbg => self.dbg = true,
381            OD::expect(spec) => store(&mut self.expect_target, spec)?,
382            OD::For(spec) => store(&mut self.driver_kind, spec)?,
383            OD::beta_deftly(be) => self.beta_enabled = Some(be),
384        })
385    }
386}