derive_deftly_macros/
modules.rs

1//! Macro modules, `define_derive_deftly_module`
2
3use super::framework::*;
4use define::TemplateDefinition;
5
6/// A single `use Module;`
7///
8/// Could come from a template definition preamble,
9/// or the start of the body of a module itself.
10#[derive(Debug, Clone)]
11pub struct UseModule {
12    name: ModulePath,
13    #[allow(unused)]
14    beta_mods: beta::Enabled, // Remove when modules are no longer beta
15}
16
17/// Nonempty list of `use Module;` statements.
18#[derive(Debug, Clone)]
19pub struct SomeUseModules {
20    outer: UseModule,
21    uses: Vec<UseModule>,
22}
23
24/// A `${define}` or `${defcond}`, in `TokenStream`-like form
25///
26/// Is always dollar-escaped (`$orig_dollar`), when converted to tokens.
27///
28/// Invariant: it *is* one of those two, in correct syntax.
29#[derive(Debug)]
30pub struct SharedDef {
31    stream_d_escaped: TokenStream,
32}
33
34pub type SharedDefs = Concatenated<SharedDef>;
35
36/// The input to `define_derive_deftly_module!`
37#[derive(Debug)]
38pub struct ModuleDefinition {
39    doc_attrs: DocAttributes,
40    export: Option<MacroExport>,
41    name: ModuleName,
42    uses: Vec<UseModule>,
43    defs: SharedDefs,
44    #[allow(unused)]
45    beta_mods: beta::Enabled, // Remove when modules are no longer beta
46}
47
48/// The PREFIX inside a `derive_deftly_engine!` call to define a template
49#[derive(Debug, Default)]
50pub struct ImportedDefinitions {
51    parsed: Template<TokenAccumulator>,
52}
53impl Deref for ImportedDefinitions {
54    type Target = Template<TokenAccumulator>;
55    fn deref(&self) -> &Template<TokenAccumulator> {
56        &self.parsed
57    }
58}
59
60impl ModuleDefinition {
61    fn parse(
62        input: ParseStream,
63        beta_mods: beta::Enabled,
64    ) -> syn::Result<Self> {
65        let doc_attrs = input.parse()?;
66        let export = MacroExport::parse_option(input)?;
67        let name = input.parse()?;
68        let mut options = DdOptions::default();
69        options.parse_update(input, OpContext::ModuleDefinition)?;
70        let beta_content = options.beta_enabled.ok_or(error_generator!(
71 "beta derive-deftly feature used, without `beta_deftly` module option"
72        ));
73
74        let _colon: Token![:] = input.parse()?;
75        let uses = UseModule::parse_several(input)?;
76
77        let defs = SharedDefs::parse(
78            input,
79            beta_content,
80            OrigDollarHandledDiscriminants::NotFound,
81        )?;
82
83        Ok(ModuleDefinition {
84            doc_attrs,
85            export,
86            name,
87            uses,
88            defs,
89            beta_mods,
90        })
91    }
92}
93
94impl SharedDefs {
95    fn parse(
96        input: ParseStream,
97        beta_content: beta::MaybeEnabled,
98        exp_d_escaped: OrigDollarHandledDiscriminants,
99    ) -> syn::Result<Self> {
100        // We want to:
101        // 1. parse a `${define...}` or `${defcond...}` to check it ssyntax.
102        // 2. somehow capture what we parsed as a TokenStream
103        // 3. Make sure that we dollar-escape it if it wasn't already.
104
105        // Our approach is:
106        //
107        //  * Use `ParseBuffer::fork` to obtain a separate cursor.
108        //  * Parse from there into a `Template`.
109        //  * Check that `Template` is what we expect.
110        //  * With the original ParseStream, manually fish out
111        //   `$`, the `orig_dollar`, and the `Group`.
112        //    Escape the `Group` if we need to.
113        //  * Check that the cursor is the3 same as we just had.
114        //  * Reassemble a ts from those manually-disassembled pieces.
115
116        let mut defs = vec![];
117        while input.peek(Token![$]) {
118            let def_end_cursor = {
119                let input = input.fork();
120                let subst = beta::with_maybe_enabled(beta_content, || {
121                    Subst::<TokenAccumulator>::parse_entire(&input)
122                })?;
123                match subst.sd {
124                    SD::define(..) | SD::defcond(..) => {}
125                    _other => return Err(subst.kw_span.error(
126                        "only ${define..} and ${defcond..} are allowed here",
127                    )),
128                }
129                input.cursor()
130            };
131
132            let stream_d_escaped = (|| {
133                let mut out = TokenStream::new();
134
135                let dollar: Token![$] = input.parse()?;
136                dollar.to_tokens(&mut out);
137
138                let got_d_escaped = deescape_orig_dollar(input)?;
139
140                if got_d_escaped.discriminant() != exp_d_escaped {
141                    return Err(dollar.error(format_args!(
142                        "escaping not as expected: got={:?}, exp={:?}",
143                        exp_d_escaped,
144                        got_d_escaped.discriminant(),
145                    )));
146                }
147
148                let g_in: proc_macro2::Group = input.parse()?;
149
150                let o_d_span;
151                let g_out;
152
153                match got_d_escaped {
154                    OrigDollarHandled::NotFound => {
155                        o_d_span = dollar.span();
156                        g_out = group_clone_set_stream(
157                            &g_in,
158                            escape_dollars(g_in.stream()),
159                        );
160                    }
161                    OrigDollarHandled::Found(span) => {
162                        // The outer $ has been escaped as expected.
163                        // Trust the inner input is (preperly) escaped too.
164                        // This isn't an adversarial context.
165                        o_d_span = span;
166                        g_out = g_in;
167                    }
168                }
169
170                out.extend(quote_spanned! {o_d_span=> orig_dollar });
171                g_out.to_tokens(&mut out);
172
173                if input.cursor() != def_end_cursor {
174                    return Err(input.error(
175 "shared definitions precheck desynchronised with ad-hoc TS extraction"
176                    ));
177                }
178
179                Ok(out)
180            })()
181            .map_err(adviseable::advise_incompatibility)?;
182
183            defs.push(SharedDef { stream_d_escaped })
184        }
185        Ok(Concatenated(defs))
186    }
187}
188
189impl UseModule {
190    pub fn parse_several(input: ParseStream) -> syn::Result<Vec<Self>> {
191        let mut uses = vec![];
192        while input.peek(Token![use]) {
193            let use_kw: Token![use] = input.parse()?;
194            let kw_span = use_kw.span();
195            let name = input.parse()?;
196            let _: Token![;] = input.parse()?;
197            uses.push(UseModule {
198                name,
199                beta_mods: beta::Enabled::new_for_modules_feature(kw_span)?,
200            });
201        }
202        Ok(uses)
203    }
204}
205
206impl SomeUseModules {
207    /// If there are some `use`s, return a `SomeUseModules` to handle them
208    pub fn divert(mut uses: Vec<UseModule>) -> Option<Self> {
209        let outer = uses.pop()?;
210        Some(SomeUseModules { outer, uses })
211    }
212
213    /// Output a suitable `via_modules` macro call
214    pub fn output(
215        self,
216        mode: syn::Ident,
217        new_prefix_d_escaped: TokenStream,
218        main_passthrough: TokenStream,
219    ) -> syn::Result<TokenStream> {
220        let use_outer = self.outer.name.macro_path();
221        let uses = self
222            .uses
223            .into_iter()
224            .map(|u| {
225                let path = u.name.macro_path();
226                quote! { #path {} }
227            })
228            .collect_vec();
229
230        let engine = engine_macro_name()?;
231
232        Ok(quote! {
233            #use_outer! {
234                via_modules [ {} #(#uses)* #engine {} #mode () ]
235                { #new_prefix_d_escaped }
236                { #main_passthrough }
237                ( $ )
238            }
239        })
240    }
241}
242
243impl ToTokens for SharedDef {
244    fn to_tokens(&self, out: &mut TokenStream) {
245        self.stream_d_escaped.to_tokens(out);
246    }
247}
248
249impl Parse for ImportedDefinitions {
250    fn parse(input: ParseStream) -> syn::Result<Self> {
251        let impossible = || {
252            adviseable::advise_incompatibility(
253                input.error("unsupported content in imported definitions"),
254            )
255        };
256
257        let parsed: Template<TokenAccumulator> = beta::with_maybe_enabled(
258            beta::Enabled::new_for_imported_definitions(),
259            || input.parse(),
260        )?;
261
262        for te in &parsed.elements {
263            let subst = match te {
264                TE::Subst(subst) => subst,
265                _other => return Err(impossible()),
266            };
267            match &subst.sd {
268                SD::define(..) | SD::defcond(..) => {}
269                _other => return Err(impossible()),
270            }
271        }
272
273        Ok(ImportedDefinitions { parsed })
274    }
275}
276
277struct ExpansionsAndAttrsOnly;
278impl SpecialParseContext for ExpansionsAndAttrsOnly {
279    fn before_element_hook(
280        &mut self,
281        input: ParseStream,
282    ) -> syn::Result<Option<SpecialInstructions>> {
283        Ok(
284            if input.peek(Token![$])
285                || input.peek(Token![#])
286                || input.peek(token::Bracket)
287            // any other groups besides #[] will be spotted later
288            {
289                None
290            } else {
291                Some(SpecialInstructions::EndOfTemplate)
292            },
293        )
294    }
295}
296
297impl TemplateDefinition {
298    /// Expands the docs, and parses the rest of the template
299    pub fn parse_from_via_modules(
300        prefix_d_escaped: ParseStream,
301        main: ParseStream,
302    ) -> syn::Result<Self> {
303        let prefix_d_escaped: TokenStream = prefix_d_escaped.parse()?;
304        let imported_definitions: ImportedDefinitions =
305            syn::parse2(prefix_d_escaped.clone())?;
306
307        let doc_attrs = beta::with_maybe_enabled(
308            Err(error_generator!(
309 "beta template features are not available in the docs preamble, even with beta_deftly in the template options; but you could put the beta use into a reuseable module"
310            )),
311            || {
312                Template::<TokenAccumulator>::parse_special(
313                    main,
314                    &mut ExpansionsAndAttrsOnly,
315                )
316            },
317        )?;
318
319        let mut definition: TemplateDefinition =
320            // This will harmlessly look again for some doc attributes,
321            // bu we just consumed all of those from `main`.
322            main.parse()?;
323
324        definition.doc_attrs = {
325            let mut expanded = TokenAccumulator::new();
326            Template::expand_iter(
327                chain!(
328                    &imported_definitions.parsed.elements,
329                    &doc_attrs.elements,
330                ),
331                GeneralContext::NoDriver(&DriverlessContext {
332                    defs: DefinitionsContext::default(),
333                    driver_needed: error_generator!(
334 "not allowed outside the main template body; no driver data structure here"
335                    ),
336                }),
337                &mut expanded,
338            );
339            let expanded = expanded.tokens()?;
340            syn::parse2(expanded)?
341        };
342        definition.imported_definitions_d_escaped = prefix_d_escaped;
343
344        // We thread our code through macro_rules macros, which like to do
345        // hygiene to them - but we want all our identifiers to have the same
346        // span, namely the span they would have had if they'd not been in a
347        // module - that's Span::call_site, meaning (without modules) the call
348        // site of define_derive_deftly and with modules the call site of the
349        // engine, which is ultimately the same place.
350        let respan_span = Span::call_site();
351        let respan = |ts: &mut TokenStream| {
352            *ts = respan_hygiene(ts.clone(), respan_span)
353        };
354        respan(&mut definition.imported_definitions_d_escaped);
355        respan(&mut definition.template);
356        // The following fields in TemplateDefinition aren't respanned:
357        //  * doc_attrs: ought not to contain references to local variables
358        //  * export: just an `export` keyword, span is not importsnt
359        //  * templ_name: came from the real template definition call site, ok
360        //  * options: just options for us, no local variables
361
362        Ok(definition)
363    }
364}
365
366impl ModuleDefinition {
367    pub fn parse_from_via_modules(
368        prefix_d_escaped: ParseStream,
369        main: ParseStream,
370        beta_mods: beta::Enabled,
371    ) -> syn::Result<Self> {
372        let mut mod_def = ModuleDefinition::parse(main, beta_mods)?;
373        let imported = SharedDefs::parse(
374            prefix_d_escaped,
375            beta::Enabled::new_for_imported_definitions(),
376            OrigDollarHandledDiscriminants::Found,
377        )?;
378        mod_def.defs =
379            Concatenated(chain!(imported.0, mod_def.defs.0,).collect_vec());
380        Ok(mod_def)
381    }
382}
383
384/// This is `define_derive_deftly_module!`
385pub fn define_derive_deftly_module(
386    input: TokenStream,
387) -> Result<TokenStream, syn::Error> {
388    dprint_block!(&input, "define_derive_deftly_module! input");
389
390    let beta_mods = beta::Enabled::new_for_modules_feature(input.span())?;
391
392    let md = Parser::parse2(
393        |input: ParseStream<'_>| ModuleDefinition::parse(input, beta_mods),
394        input,
395    )?;
396
397    define_module(md, "define_derive_deftly_module! output")
398}
399
400pub fn define_module(
401    md: ModuleDefinition,
402    #[allow(unused)] dprint_prefix: &str,
403) -> syn::Result<TokenStream> {
404    let ModuleDefinition {
405        doc_attrs,
406        export,
407        name,
408        uses,
409        defs,
410        beta_mods: _,
411    } = md;
412
413    let export_attr = export.map(|pub_token| {
414        let span = pub_token.span();
415        quote_spanned!(span=> #[macro_expor])
416    });
417
418    let mac_name = name.macro_name();
419    let doc_attrs = doc_attrs.to_tokens_with_addendum(format_args!(
420        r#"
421
422This is a `derive_deftly` reuseable template code module.
423Do not invoke it directly."#,
424    ));
425
426    let mk_return = |output| {
427        dprint_block!(&output, "{} {}", dprint_prefix, mac_name);
428        Ok(output)
429    };
430
431    if let Some(divert) = SomeUseModules::divert(uses) {
432        return mk_return(divert.output(
433            format_ident!("defmod"),
434            defs.to_token_stream(),
435            quote! { #doc_attrs #export_attr #name: },
436        )?);
437    }
438
439    mk_return(quote! {
440        #doc_attrs
441        #export_attr
442        macro_rules! #mac_name {
443            {
444                via_modules [
445                    { $($our_opts:tt)* }
446                    $next_macro:path
447                { $($next_opts:tt)* }
448                    $($rest:tt)*
449                ]
450                { $($predefs:tt)* }
451                { $($main:tt)* }
452                ( $($extra:tt)* )
453                $($ignored:tt)*
454            } => {
455                $next_macro! {
456                    via_modules [ { $($next_opts)* } $($rest)* ]
457                    { #defs $($predefs)* }
458                    { $($main)* }
459                    ( $($extra)* )
460                }
461            };
462            { $($wrong:tt)* } => {
463                compile_error!{concat!(
464                    "wrong input to derive-deftly module macro ",
465                    stringify!(#mac_name: $($wrong)*),
466                    "; might be due to incompatible derive-deftly versions(s)",
467                )}
468            };
469        }
470    })
471}