derive_deftly_macros/
engine.rs

1//! `derive_deftly_engine!()`
2
3use super::framework::*;
4use adviseable::*;
5use modules::ImportedDefinitions;
6
7/// Input to `derive_deftly_engine!`, principal form (template expansion)
8///
9/// See `implementation.md`,
10/// especially
11/// "Overall input syntax for `derive_deftly_engine!` and templates".
12#[derive(Debug)]
13struct EngineExpandInput {
14    driver: syn::DeriveInput,
15    options: DdOptions,
16    imported_definitions: ImportedDefinitions,
17    template: TopTemplate,
18    template_crate: syn::Path,
19    template_name: Option<syn::Path>,
20    chain_next: Option<ChainNext>,
21    chain_after: TokenStream,
22    accum: TokenStream,
23}
24
25#[derive(Debug)]
26pub struct ChainNext {
27    pub call: TokenStream,
28    pub after_driver: TokenStream,
29}
30
31enum EngineContext {
32    Expand {
33        opcontext_template: OpContext,
34        options: DdOptions,
35    },
36    Final {},
37}
38
39#[derive(Debug)]
40enum EngineInput {
41    Expand(EngineExpandInput),
42    Final(accum::EngineFinalInput),
43    DefinitionViaModules(define::TemplateDefinition),
44    ModuleDefinitionViaModules(modules::ModuleDefinition),
45}
46
47impl Parse for ChainNext {
48    fn parse(input: ParseStream) -> syn::Result<Self> {
49        let call = input.parse::<syn::Path>()?.to_token_stream();
50        let after_driver;
51        let _ = parenthesized!(after_driver in input);
52        let after_driver = after_driver.parse()?;
53        Ok(ChainNext { call, after_driver })
54    }
55}
56
57impl ToTokens for ChainNext {
58    fn to_tokens(&self, out: &mut TokenStream) {
59        let ChainNext { call, after_driver } = self;
60        quote!( #call (#after_driver) ).to_tokens(out);
61    }
62}
63
64fn parse_via_mdoules_invocation(
65    input: ParseStream,
66) -> AdviseableResult<EngineInput> {
67    let chain; // Inside [ ] after via_modules
68    let this_args; // Inside { } and [ ], our arguments
69    let prefix_d_escaped;
70    let main;
71    let extra; // inside the final ( $ ... )
72
73    let _ = bracketed!(chain in input);
74    let _ = braced!(this_args in chain);
75    let mode = syn::Ident::parse_any(&chain)?;
76    let _ = braced!(prefix_d_escaped in input);
77    let _ = braced!(main in input);
78    let _ = parenthesized!(extra in input);
79    let _dollar: Token![$] = extra.parse()?;
80
81    let r = if mode == "define" {
82        let def = parse_unadvised! { main => || {
83            define::TemplateDefinition::parse_from_via_modules(
84                &prefix_d_escaped,
85                main,
86            )
87        } };
88
89        EngineInput::DefinitionViaModules(def)
90    } else if mode == "defmod" {
91        let beta = beta::Enabled::new_for_modules_feature(mode.span())?;
92        let def = parse_unadvised! { main => || {
93            modules::ModuleDefinition::parse_from_via_modules(
94                &prefix_d_escaped,
95                main,
96                beta,
97            )
98        } };
99
100        EngineInput::ModuleDefinitionViaModules(def)
101    } else {
102        return Err(
103            mode.error(format!("unknown engine via_modules mode `{}`", mode))
104        );
105    };
106
107    // Discard at three places where we leave room for expansion
108    let _: TokenStream = extra.parse()?;
109    let _: TokenStream = this_args.parse()?;
110    let _: TokenStream = chain.parse()?;
111
112    Ok(AOk(r))
113}
114
115impl ParseAdviseable for EngineInput {
116    fn parse_adviseable(input: ParseStream) -> AdviseableResult<Self> {
117        if input.peek(Ident::peek_any) {
118            let special = syn::Ident::parse_any(input)?;
119            return if special == "via_modules" {
120                parse_via_mdoules_invocation(input)
121            } else {
122                Err(special.error(format!(
123                    "unknown engine special expansion request `{}`",
124                    special,
125                )))
126            };
127        }
128
129        let driver;
130        let _ = braced!(driver in input);
131        let driver = driver.parse()?;
132
133        let engine_context;
134        if input.peek(syn::token::Bracket) {
135            // AOPTIONS appears iff we're being invoked for a precanned
136            // template, rather than an adhoc one; it's from the
137            // `#[derive()` application.
138            let tokens;
139            let mut options = DdOptions::default();
140
141            let _ = bracketed!(tokens in input);
142            parse_unadvised! {
143                tokens => || {
144                    let oc = OpContext::DriverApplicationPassed;
145                    options .parse_update(&tokens, oc)
146                }
147            }
148            engine_context = EngineContext::Expand {
149                opcontext_template: OpContext::TemplateDefinition,
150                options,
151            };
152        } else if input.peek(Token![.]) {
153            let _indicator: Token![.] = input.parse()?;
154            engine_context = EngineContext::Final {};
155        } else {
156            engine_context = EngineContext::Expand {
157                opcontext_template: OpContext::TemplateAdhoc,
158                options: DdOptions::default(),
159            };
160        }
161
162        let future_ignored;
163        let _ = parenthesized!(future_ignored in input);
164        let _: TokenStream = future_ignored.parse()?;
165
166        let r = match engine_context {
167            EngineContext::Expand {
168                opcontext_template,
169                options,
170            } => EngineExpandInput::parse_adviseable_remainder(
171                driver,
172                options,
173                input,
174                opcontext_template,
175            )?
176            .map(EngineInput::Expand),
177            EngineContext::Final {} => {
178                accum::EngineFinalInput::parse_adviseable_remainder(
179                    driver, input,
180                )?
181                .map(EngineInput::Final)
182            }
183        };
184        Ok(r)
185    }
186}
187
188impl EngineExpandInput {
189    fn parse_adviseable_remainder(
190        driver: syn::DeriveInput,
191        mut options: DdOptions,
192        input: ParseStream,
193        opcontext_template: OpContext,
194    ) -> AdviseableResult<Self> {
195        let template;
196        let _ = braced!(template in input);
197
198        let template_crate;
199        let template_name;
200        let imported_definitions;
201        {
202            let through_driver;
203            let _ = parenthesized!(through_driver in input);
204            let input = through_driver;
205
206            template_crate = input.parse()?;
207            let _: Token![;] = input.parse()?;
208
209            let tokens;
210            let _ = bracketed!(tokens in input);
211            parse_unadvised! {
212                tokens => || {
213                    options.parse_update(&tokens, opcontext_template)
214                }
215            }
216
217            template_name = if input.peek(Token![;]) {
218                None
219            } else {
220                Some(input.parse()?)
221            };
222            let _: Token![;] = input.parse()?;
223
224            imported_definitions = if !input.is_empty() {
225                let imported_definitions;
226                let _ = braced!(imported_definitions in input);
227                imported_definitions.parse()?
228            } else {
229                ImportedDefinitions::default()
230            };
231
232            let _: TokenStream = input.parse()?;
233        }
234
235        let (chain_next, chain_after);
236        {
237            let chain;
238            let _ = bracketed!(chain in input);
239            let input = chain;
240            chain_next = if !input.is_empty() {
241                Some(input.parse()?)
242            } else {
243                None
244            };
245            chain_after = input.parse()?;
246        }
247
248        let accum;
249        let _ = bracketed!(accum in input);
250        let accum = accum.parse()?;
251
252        let _: TokenStream = input.parse()?;
253
254        let template = parse_unadvised! {
255            template => || TopTemplate::parse(
256                template,
257                options.beta_enabled,
258            )
259        };
260
261        Ok(AOk(EngineExpandInput {
262            driver,
263            options,
264            imported_definitions,
265            template,
266            template_crate,
267            template_name,
268            chain_next,
269            chain_after,
270            accum,
271        }))
272    }
273}
274
275impl<'c> Context<'c> {
276    /// Calls `f` with a top-level [`Context`] for a [`syn::DeriveInput`]
277    ///
278    /// `Context` has multiple levels of references to values created
279    /// here, so we can't easily provide `Context::new()`.
280    pub fn call<T>(
281        driver: &syn::DeriveInput,
282        template_crate: &syn::Path,
283        template_name: Option<&syn::Path>,
284        f: impl FnOnce(Context) -> syn::Result<T>,
285    ) -> Result<T, syn::Error> {
286        let tmetas = preprocess_attrs(&driver.attrs)?;
287
288        let pvariants_one = |fields| {
289            let pmetas = &tmetas;
290            let pfields = preprocess_fields(fields)?;
291            let pvariant = PreprocessedVariant {
292                fields,
293                pmetas,
294                pfields,
295            };
296            syn::Result::Ok((Some(()), vec![pvariant]))
297        };
298
299        let union_fields;
300        let variants_pmetas: Vec<_>;
301
302        let (variant, pvariants) = match &driver.data {
303            syn::Data::Struct(ds) => pvariants_one(&ds.fields)?,
304            syn::Data::Union(du) => {
305                union_fields = syn::Fields::Named(du.fields.clone());
306                pvariants_one(&union_fields)?
307            }
308            syn::Data::Enum(de) => (None, {
309                variants_pmetas = de
310                    .variants
311                    .iter()
312                    .map(|variant| preprocess_attrs(&variant.attrs))
313                    .try_collect()?;
314                izip!(&de.variants, &variants_pmetas)
315                    .map(|(variant, pmetas)| {
316                        let fields = &variant.fields;
317                        let pfields = preprocess_fields(&variant.fields)?;
318                        Ok(PreprocessedVariant {
319                            fields,
320                            pmetas,
321                            pfields,
322                        })
323                    })
324                    .collect::<Result<Vec<_>, syn::Error>>()?
325            }),
326        };
327
328        // `variant` is None in enums; otherwise it's Some(())
329        // and here we convert it to the real WithinVariant for the fields.
330        let variant = variant.map(|()| WithinVariant {
331            variant: None, // not actually a variant
332            fields: pvariants[0].fields,
333            pmetas: &pvariants[0].pmetas,
334            pfields: &pvariants[0].pfields,
335            index: 0,
336        });
337
338        let ctx = Context {
339            top: &driver,
340            template_crate,
341            template_name,
342            pmetas: &tmetas,
343            field: None,
344            variant: variant.as_ref(),
345            pvariants: &pvariants,
346            defs: DefinitionsContext {
347                defs: Default::default(),
348                nesting_depth: 0,
349                nesting_parent: None,
350            },
351            within_loop: WithinLoop::None,
352        };
353
354        f(ctx)
355    }
356}
357
358impl EngineExpandInput {
359    fn process(self) -> syn::Result<TokenStream> {
360        dprintln!("derive_deftly_engine! crate = {:?}", &self.template_crate);
361
362        let DdOptions {
363            dbg,
364            driver_kind,
365            expect_target,
366            beta_enabled,
367            //
368        } = self.options;
369
370        // This was used when parsing EngineExpandInput.template
371        let _: Option<_> = beta_enabled;
372
373        if let Some(exp) = driver_kind {
374            macro_rules! got_kind { { $($kind:ident)* } => {
375                match &self.driver.data {
376                    $(
377                        syn::Data::$kind(..) => ExpectedDriverKind::$kind,
378                    )*
379                }
380            } }
381
382            let got_kind = got_kind!(Struct Enum Union);
383            if got_kind != exp.value {
384                return Err([
385                    (exp.span, "expected kind"),
386                    (self.driver.span(), "actual kind"),
387                ]
388                .error(format_args!(
389                    "template defined for {}, but applied to {}",
390                    exp.value, got_kind,
391                )));
392            }
393        }
394
395        let outcome = Context::call(
396            &self.driver,
397            &self.template_crate,
398            self.template_name.as_ref(),
399            |ctx| {
400                let mut output = TokenAccumulator::new();
401                Template::expand_iter(
402                    chain!(
403                        &self.imported_definitions.elements,
404                        &self.template.elements,
405                    ),
406                    ctx.as_general(),
407                    &mut output,
408                );
409                let output = output.tokens()?;
410
411                //    dbg!(&&output);
412                if dbg {
413                    let description = ctx.expansion_description();
414                    let dump = format!(
415                        concat!(
416                            "---------- {} (start) ----------\n",
417                            "{}\n",
418                            "---------- {} (end) ----------\n",
419                        ),
420                        &description, &output, &description,
421                    );
422                    eprint!("{}", dump);
423                }
424
425                let mut output = output;
426                if let Some(target) = expect_target {
427                    check::check_expected_target_syntax(
428                        &ctx,
429                        &mut output,
430                        target,
431                    );
432                }
433
434                let metas_used = ctx.encode_metas_used();
435
436                Ok((output, metas_used))
437            },
438        );
439
440        let (expanded, metas_used) = match outcome {
441            Ok((expanded, metas_used)) => (Ok(expanded), Ok(metas_used)),
442            Err(e) => (Err(e), Err(())),
443        };
444
445        let chain_call;
446
447        if let Some(ChainNext { call, after_driver }) = &self.chain_next {
448            let driver = &self.driver;
449            let chain_after = &self.chain_after;
450
451            let mut accum = self.accum.to_token_stream();
452            if let Some(name) = &self.template_name {
453                accum.extend(quote!( _name [#name] ));
454            }
455            match &metas_used {
456                Ok(metas_used) => {
457                    accum.extend(quote!( _meta_used #metas_used ));
458
459                    use meta::FindRecogMetas as _;
460                    let mut meta_recog = meta::Recognised::default();
461                    self.template.find_recog_metas(&mut meta_recog);
462                    accum.extend(quote!( _meta_recog [#meta_recog] ));
463                }
464                Err(()) => {
465                    accum.extend(quote!( _error [] ));
466                }
467            }
468
469            chain_call = quote! {
470                #call! {
471                    { #driver }
472                    #after_driver
473                    [ #chain_after ]
474                    [ #accum ]
475                }
476            }
477        } else {
478            chain_call = TokenStream::new();
479        };
480
481        let mut out = expanded.unwrap_or_else(|e| e.into_compile_error());
482        dprint_block!(&out, "derive_deftly_engine! expansion output");
483        dprint_block!(&chain_call, "derive_deftly_engine! chain call");
484        out.extend(chain_call);
485
486        Ok(out)
487    }
488}
489
490/// `derive_deftly_engine!` -- implements the actual template engine
491///
492/// In my design, the input contains, firstly, literally the definition
493/// that #[derive(Deftly)] was applied to (see NOTES.txt).
494/// Using the literal input, rather than some pre-parsed version, is
495/// slower, but means that we aren't inventing a nontrivial data format which
496/// potentially crosses crate boundaries with semver implications.
497pub fn derive_deftly_engine_func_macro(
498    input: TokenStream,
499) -> syn::Result<TokenStream> {
500    dprint_block!(&input, "derive_deftly_engine! input");
501    let input: EngineInput = adviseable_parse2(input)?;
502    match input {
503        EngineInput::Expand(i) => i.process(),
504        EngineInput::Final(i) => i.process(),
505        EngineInput::DefinitionViaModules(def) => define::define_template(
506            def,
507            "derive_deftly_engine! output, define_derive_deftly via modules",
508        ),
509        EngineInput::ModuleDefinitionViaModules(def) => {
510            modules::define_module(
511                def,
512                "derive_deftly_engine! output, define module via modules",
513            )
514        }
515    }
516}