derive_deftly_macros/
define.rs

1//! Macro impl for defining a template `define_derive_deftly!`
2
3use super::framework::*;
4
5#[derive(Debug, Clone)]
6pub struct TemplateDefinition {
7    // pub for the benefit of mmodules
8    pub imported_definitions_d_escaped: TokenStream,
9    pub doc_attrs: DocAttributes,
10    export: Option<MacroExport>,
11    pub templ_name: TemplateName,
12    options: UnprocessedOptions,
13    pub template: TokenStream,
14}
15
16#[derive(Debug, Clone)]
17enum DefineMacroInput {
18    WithUses {
19        uses: modules::SomeUseModules,
20        rest: TokenStream,
21    },
22    Immediate(TemplateDefinition),
23}
24
25impl Parse for DefineMacroInput {
26    fn parse(input: ParseStream) -> syn::Result<Self> {
27        let uses = modules::UseModule::parse_several(input)?;
28        if let Some(uses) = modules::SomeUseModules::divert(uses) {
29            let rest = input.parse()?;
30            return Ok(DefineMacroInput::WithUses { uses, rest });
31        }
32
33        input.parse().map(DefineMacroInput::Immediate)
34    }
35}
36
37impl Parse for TemplateDefinition {
38    fn parse(input: ParseStream) -> syn::Result<Self> {
39        // This rejects Rust keywords, which is good because
40        // for example `#[derive_deftly(pub)]` ought not to mean to apply
41        // a template called `pub`.  See ticket #1.
42        let doc_attrs = input.parse()?;
43        if let Some::<Token![$]>(dollar) = input.parse()? {
44            return Err(dollar.error(
45 "expected docs attributes and template name (note that expansions in template docs are only allowed if modules were imported with use `use`)"
46            ));
47        }
48        let export = MacroExport::parse_option(input)?;
49        let templ_name = input.parse()?;
50        let options =
51            UnprocessedOptions::parse(&input, OpContext::TemplateDefinition)?;
52        let la = input.lookahead1();
53        if la.peek(Token![=]) {
54            let equals: Token![=] = input.parse()?;
55            return Err(equals.error(
56 "You must now write `define_derive_deftly! { Template: ... }`, not `Template =`, since derive-deftly version 0.14.0"
57            ));
58        } else if la.peek(Token![:]) {
59            let _colon: Token![:] = input.parse()?;
60        } else {
61            return Err(la.error());
62        };
63        let template = input.parse()?;
64        Ok(TemplateDefinition {
65            imported_definitions_d_escaped: TokenStream::new(),
66            doc_attrs,
67            export,
68            templ_name,
69            options,
70            template,
71        })
72    }
73}
74
75/// Replaces every `$` with `$orig_dollar`
76///
77/// Eg, where the template says `$fname`, we emit `$orig_dollar fname`.
78/// When this is found in the macro_rules expander part
79/// of a precanned template,
80/// macro_rules doesn't expand
81/// it because `orig_dollar` isn't one of the arguments to the macro.
82///
83/// Then, we spot these when parsing the template, and disregard them.
84/// That is done by
85/// [`syntax::deescape_orig_dollar`](super::syntax::deescape_orig_dollar).
86///
87/// See `doc/implementation.md` for why this is needed.
88///
89/// This has the weird result that there's sometimes
90/// (namely, when using an adhoc, rather than precanned template)
91/// an undocumented `orig_dollar` expansion keyword,
92/// with strange behaviour.
93/// No-one is likely to notice this.
94///
95/// Additionally, if we're turning `$crate` into `$orig_dollar crate`,
96/// we change the keyword `crate` to `_dd_intern_crate`
97/// (and `${crate}` likewise), with the span of the original.
98/// This is necessary to avoid clippy seeing the bare `crate`
99/// and thinking the user should have written `$crate`
100/// (whereas, in fact, they did),
101/// and emitting a spurious lint `crate_in_macro_def`.
102/// `$_dd_intern_crate` is an internal alias for d-d's `$crate`.
103///
104/// ### Alternative tactics we rejected:
105///
106///  * Pass a literal dollar sign `$` into the template pattern macro,
107///    capture it with a macro rules parameter `$dollar:tt`,
108///    and write `$dollar` in the template.
109///    This gets the span wrong: the span is that of
110///    the literal dollar, which came from the call site, not the template.
111///
112/// * Use a different syntax in precanned templates:
113///   have `escape_dollars` convert to that syntax,
114///   and the template parsing notice this case and
115///   de-escape the whole template again at the start.
116///   This involves processing the whole template twice for no reason.
117///   (And it would involve inventing an additional, different,
118///   and probably weird, syntax.)
119///
120/// * As above but do the de-escaping on the fly.
121///   Currently, though, the information about the template context
122///   is not available to the parser.
123///   We'd have to pass it in as a thread local,
124///   or as an extra generic on `SubstContext`
125///   (producing two monomorphised copies of the whole template engine).
126pub fn escape_dollars(input: TokenStream) -> TokenStream {
127    enum St {
128        Dollar,
129        DollarBrace,
130        Other,
131    }
132
133    impl St {
134        fn exp_kw(&self) -> bool {
135            match self {
136                St::Dollar | St::DollarBrace => true,
137                St::Other => false,
138            }
139        }
140    }
141
142    fn handle_tt(itt: TokenTree, st: St, out: &mut TokenStream) -> St {
143        let ott = match itt {
144            TT::Group(g) => {
145                let delim = g.delimiter();
146                let span = g.span_open();
147                let stream = g.stream();
148                let st = match (st, delim) {
149                    (St::Dollar, Delimiter::Brace) => St::DollarBrace,
150                    _ => St::Other,
151                };
152                let stream = handle_ts(stream, st);
153                let g = group_new_with_span(delim, span, stream);
154                TT::Group(g)
155            }
156            TT::Punct(p) if p.as_char() == '$' => {
157                out.extend(quote_spanned! {p.span()=> #p orig_dollar });
158                return St::Dollar;
159            }
160            TT::Ident(i) if st.exp_kw() && i == "crate" => {
161                out.extend(quote_spanned! {i.span()=> _dd_intern_crate });
162                return St::Other;
163            }
164            other => other,
165        };
166        out.extend([ott]);
167        St::Other
168    }
169
170    fn handle_ts(input: TokenStream, mut st: St) -> TokenStream {
171        let mut out = TokenStream::new();
172        for itt in input {
173            st = handle_tt(itt, st, &mut out);
174        }
175        out
176    }
177
178    handle_ts(input, St::Other)
179}
180
181/// This is `define_derive_deftly!`
182pub fn define_derive_deftly_func_macro(
183    input: TokenStream,
184) -> Result<TokenStream, syn::Error> {
185    dprint_block!(&input, "define_derive_deftly! input");
186
187    let input = syn::parse2(input)?;
188
189    match input {
190        DefineMacroInput::WithUses { uses, rest } => {
191            let output =
192                uses.output(format_ident!("define"), quote! {}, rest)?;
193            dprint_block!(
194                &output,
195                "define_derive_deftly! output, via modules",
196            );
197            Ok(output)
198        }
199        DefineMacroInput::Immediate(definition) => {
200            define_template(definition, "define_derive_deftly! output")
201        }
202    }
203}
204
205pub fn define_template(
206    definition: TemplateDefinition,
207    #[allow(unused)] dprint_prefix: &str,
208) -> syn::Result<TokenStream> {
209    let TemplateDefinition {
210        imported_definitions_d_escaped,
211        doc_attrs,
212        export,
213        templ_name,
214        options,
215        template,
216    } = definition;
217
218    let mut output = TokenStream::new();
219
220    let (template, parsed_template) = {
221        let mut template = template;
222        let parsed = Parser::parse2(
223            {
224                let ue = options.beta_enabled;
225                move |input: ParseStream| TopTemplate::parse(input, ue)
226            },
227            template.clone(),
228        )
229        .map_err(|e| {
230            // Make sure the error is emitted
231            e.into_compile_error().to_tokens(&mut output);
232            // But from now on, let's just use an empty template
233            template = TokenStream::new();
234            // parsed_template becomes Err(())
235            ()
236        });
237        (template, parsed)
238    };
239
240    let _: Result<TopTemplate, ()> = parsed_template;
241    let template = escape_dollars(template);
242
243    let templ_mac_name = templ_name.macro_name();
244
245    let doc_attrs = doc_attrs.to_tokens_with_addendum(format_args!(
246        r#"
247
248This is a `derive_deftly` template.  Do not invoke it directly.
249To use it, write: `#[derive(Deftly)] #[derive_deftly({})]`."#,
250        templ_name,
251    ));
252
253    let engine_macro;
254    let export_attr;
255    match export {
256        None => {
257            export_attr = quote! {};
258            engine_macro = engine_macro_name()?;
259        }
260        Some(pub_token) => {
261            let span = pub_token.span();
262            export_attr = quote_spanned!(span=> #[macro_export]);
263            engine_macro = quote_spanned!(span=> $crate::derive_deftly::derive_deftly_engine);
264        }
265    }
266
267    // the macro must recent a dollar as its first argument because
268    // it is hard to find a dollar otherwise!
269    output.extend(quote! {
270        #doc_attrs
271        #export_attr
272        macro_rules! #templ_mac_name {
273            {
274                { $($driver:tt)* }
275                [ $($aoptions:tt)* ]
276                ( $($future:tt)* )
277                $($tpassthrough:tt)*
278            } => {
279                #engine_macro! {
280                    { $( $driver )* }
281                    [ $($aoptions)* ]
282                    ()
283                    { # template }
284                    ( $crate; [#options] #templ_name;
285                      { #imported_definitions_d_escaped } )
286                    $($tpassthrough)*
287                }
288            };
289            { $($wrong:tt)* } => {
290                compile_error!{concat!(
291                    "wrong input to derive-deftly template macro ",
292                    stringify!(#templ_mac_name),
293                    "; might be due to incompatible derive-deftly versions(s)",
294                )}
295            };
296        }
297    });
298
299    dprint_block!(&output, "{} {}", dprint_prefix, templ_mac_name);
300    Ok(output)
301}