Skip to main content

tygr_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{parse_quote, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident};
5
6/// Derive `Grammar` (and `GrammarRule`) for a `struct` or `enum`.
7///
8/// `struct`s become a concatenation of their fields; `enum`s become an
9/// alternation of their variants' own fields — see `tygr`'s crate-level
10/// `Design` section for the full mapping from Rust constructs to EBNF.
11///
12/// `#[grammar(...)]` on the derived type is optional:
13/// - `name = "..."` — override the BNF rule name (defaults to the type name).
14/// - `hidden` — omit this type from BNF output; parsing/printing unaffected.
15/// - `inline` — splice this type's own definition wherever it's
16///   referenced, instead of a rule reference.
17/// - `validated` — after a successful parse, run `Validate::validate` on the
18///   value; a rejection backtracks as if the grammar hadn't matched.
19#[proc_macro_derive(Grammar, attributes(grammar))]
20pub fn derive_grammar(input: TokenStream) -> TokenStream {
21    let mut input = syn::parse_macro_input!(input as DeriveInput);
22    for param in input.generics.type_params_mut() {
23        param.bounds.push(parse_quote!(::tygr::Grammar));
24    }
25    match impl_grammar(&input) {
26        Ok(tokens) => tokens.into(),
27        Err(err) => err.to_compile_error().into(),
28    }
29}
30
31/// `Grammar` via `GrammarFrom` + [`FromStr`](std::str::FromStr) on the matched text.
32///
33/// Parses `GrammarFrom::Source`, then calls `Self::from_str` on the exact
34/// substring it matched. `Err` backtracks the parse as if nothing had
35/// matched, and is traced under the `trace`/`trace_pos` features.
36///
37/// `#[grammar(...)]` here supports `name = "..."`, `hidden`/`inline`, and
38/// `validated` (as for `#[derive(Grammar)]`) — `Validate::validate` runs
39/// after a successful conversion, independent of the conversion's own
40/// `Err`. Since the conversion can also reject an otherwise-matching
41/// `Source`, the rule's own BNF definition shows a side-condition marker.
42#[proc_macro_derive(GrammarFromStr, attributes(grammar))]
43pub fn derive_grammar_from_str(input: TokenStream) -> TokenStream {
44    derive_convert(input, Convert::FromStr)
45}
46
47/// `Grammar` via `GrammarFrom` + [`From<Source>`](From).
48///
49/// Parses `GrammarFrom::Source`, then builds `Self` with `From::from` — this
50/// conversion can't fail, so nothing is traced.
51///
52/// `#[grammar(...)]` here supports `name = "..."`, `hidden`/`inline`, and
53/// `validated` (as for `#[derive(Grammar)]`) — `From` itself can't fail, but
54/// `Validate::validate` still runs after the conversion and can reject the
55/// value.
56#[proc_macro_derive(GrammarFromOther, attributes(grammar))]
57pub fn derive_grammar_from_source(input: TokenStream) -> TokenStream {
58    derive_convert(input, Convert::From)
59}
60
61/// `Grammar` via `GrammarFrom` + [`TryFrom<Source>`](TryFrom).
62///
63/// Parses `GrammarFrom::Source`, then calls `Self::try_from`. `Err`
64/// backtracks the parse as if nothing had matched, and is traced under the
65/// `trace`/`trace_pos` features.
66///
67/// `#[grammar(...)]` here supports `name = "..."`, `hidden`/`inline`, and
68/// `validated` (as for `#[derive(Grammar)]`) — `Validate::validate` runs
69/// after a successful conversion, independent of the conversion's own
70/// `Err`. Since the conversion can also reject an otherwise-matching
71/// `Source`, the rule's own BNF definition shows a side-condition marker.
72#[proc_macro_derive(GrammarTryFromOther, attributes(grammar))]
73pub fn derive_grammar_try_from_source(input: TokenStream) -> TokenStream {
74    derive_convert(input, Convert::TryFrom)
75}
76
77#[derive(Clone, Copy)]
78enum Convert {
79    FromStr,
80    From,
81    TryFrom,
82}
83
84fn derive_convert(input: TokenStream, convert: Convert) -> TokenStream {
85    let mut input = syn::parse_macro_input!(input as DeriveInput);
86    for param in input.generics.type_params_mut() {
87        param.bounds.push(parse_quote!(::tygr::Grammar));
88    }
89    match impl_convert(&input, convert) {
90        Ok(tokens) => tokens.into(),
91        Err(err) => err.to_compile_error().into(),
92    }
93}
94
95/// Expands a string literal into a type-level literal token.
96///
97/// This is the *primary way* to create literal tokens — the underlying
98/// `StringEq<CharThen<CH, T>>` type is an implementation detail and should not
99/// be named directly.
100///
101/// - Single character: `StringEq!(",")` → `StringEq<CharThen<',', ()>>`
102/// - Multiple characters: `StringEq!("->")` → `StringEq<CharThen<'-', CharThen<'>', ()>>>`
103#[proc_macro]
104#[allow(non_snake_case)]
105pub fn StringEq(input: TokenStream) -> TokenStream {
106    let lit = syn::parse_macro_input!(input as syn::LitStr);
107    let value = lit.value();
108    let chars: Vec<char> = value.chars().collect();
109
110    if chars.is_empty() {
111        return syn::Error::new(
112            lit.span(),
113            "StringEq!() requires a non-empty string literal",
114        )
115        .to_compile_error()
116        .into();
117    }
118
119    let chain = build_nested_chain(&chars, |ch, rest| quote! { ::tygr::CharThen<#ch, #rest> });
120    quote! { ::tygr::StringEq<#chain> }.into()
121}
122
123/// Build a right-nested chain of literal-token types terminated by `()`.
124fn build_nested_chain<F>(items: &[char], mapper: F) -> TokenStream2
125where
126    F: Fn(&char, TokenStream2) -> TokenStream2,
127{
128    items
129        .iter()
130        .rev()
131        .fold(quote! { () }, |rest, ch| mapper(ch, rest))
132}
133
134#[proc_macro]
135#[allow(non_snake_case)]
136pub fn StringEqCI(input: TokenStream) -> TokenStream {
137    let lit = syn::parse_macro_input!(input as syn::LitStr);
138    let value = lit.value();
139    let chars: Vec<char> = value.chars().collect();
140
141    if chars.is_empty() {
142        return syn::Error::new(
143            lit.span(),
144            "StringEqCI!() requires a non-empty string literal",
145        )
146        .to_compile_error()
147        .into();
148    }
149
150    let chain = build_nested_chain(&chars, |ch, rest| quote! { ::tygr::CharCIThen<#ch, #rest> });
151    quote! { ::tygr::StringEqCI<#chain> }.into()
152}
153
154#[derive(Clone)]
155struct Tag {
156    name: TokenStream2,
157    case: Option<TokenStream2>,
158}
159
160impl Tag {
161    fn new(name: TokenStream2, case: Option<TokenStream2>) -> Self {
162        Self { name, case }
163    }
164
165    fn as_constructor(&self) -> TokenStream2 {
166        let name = &self.name;
167        let case = if let Some(case) = &self.case {
168            quote! { :: #case }
169        } else {
170            quote! {}
171        };
172        quote! { #name #case }
173    }
174}
175
176fn with_node(inline: bool, body: TokenStream2) -> TokenStream2 {
177    if inline {
178        body
179    } else {
180        quote! {
181            let mut state = state.node(Self::NAME, pos);
182            #body
183        }
184    }
185}
186
187/// The BNF rule name a type gets when `#[grammar(name = "...")]` isn't given.
188fn default_name(ident: &Ident) -> String {
189    let ident = ident.to_string();
190    #[cfg(all(feature = "lower_bnf_name", not(feature = "upper_bnf_name")))]
191    let ident = ident.to_ascii_lowercase();
192    #[cfg(all(feature = "upper_bnf_name", not(feature = "lower_bnf_name")))]
193    let ident = ident.to_ascii_uppercase();
194    ident
195}
196
197fn impl_grammar(input: &DeriveInput) -> syn::Result<TokenStream2> {
198    let ident = &input.ident;
199    let generics = &input.generics;
200    let GrammarAttr {
201        name,
202        hidden,
203        inline,
204        validated,
205    } = grammar_attr(input)?;
206    let name = name.unwrap_or_else(|| default_name(ident));
207    match &input.data {
208        Data::Struct(data) => impl_struct(ident, generics, name, hidden, inline, validated, data),
209        Data::Enum(data) => impl_enum(ident, generics, name, hidden, inline, validated, data),
210        Data::Union(_) => Err(syn::Error::new_spanned(
211            ident,
212            "Grammar cannot be derived for unions",
213        )),
214    }
215}
216
217fn grammar_attr(input: &DeriveInput) -> syn::Result<GrammarAttr> {
218    for attr in &input.attrs {
219        if attr.path().is_ident("grammar") {
220            return attr.parse_args::<GrammarAttr>();
221        }
222    }
223    Ok(GrammarAttr::default())
224}
225
226#[derive(Default)]
227struct GrammarAttr {
228    name: Option<String>,
229    hidden: bool,
230    inline: bool,
231    validated: bool,
232}
233
234impl syn::parse::Parse for GrammarAttr {
235    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
236        let mut attr = GrammarAttr::default();
237
238        while !input.is_empty() {
239            let ident: syn::Ident = input.parse()?;
240            if ident == "name" {
241                let _: syn::Token![=] = input.parse()?;
242                let lit: syn::LitStr = input.parse()?;
243                attr.name = Some(lit.value());
244            } else if ident == "hidden" {
245                attr.hidden = true;
246                // Hidden nodes are not presented in BNF so they must be inline
247                // as non-inline node names are presented in traces
248                attr.inline = true;
249            } else if ident == "inline" {
250                attr.inline = true;
251            } else if ident == "validated" {
252                attr.validated = true;
253            } else {
254                return Err(syn::Error::new(
255                    ident.span(),
256                    "expected `name`, `hidden`, `inline`, or `validated`",
257                ));
258            }
259            if input.is_empty() {
260                break;
261            }
262            let _: syn::Token![,] = input.parse()?;
263        }
264
265        Ok(attr)
266    }
267}
268
269type ProcessedFields<'a> = Vec<(String, Ident, &'a syn::Type)>;
270
271fn components_and_xts(tag: TokenStream2, fields: &Fields) -> (TokenStream2, ProcessedFields<'_>) {
272    match fields {
273        Fields::Named(fields) => {
274            let fields: Vec<_> = fields
275                .named
276                .iter()
277                .map(|field| {
278                    let x = field.ident.as_ref().unwrap().clone();
279                    let t = &field.ty;
280                    (x.to_string(), x, t)
281                })
282                .collect();
283            let xs = fields.iter().map(|(_, x, _)| x);
284            (quote! { #tag { #(#xs),* } }, fields)
285        }
286        Fields::Unnamed(fields) => {
287            let fields: Vec<_> = fields
288                .unnamed
289                .iter()
290                .enumerate()
291                .map(|(i, field)| {
292                    let x = Ident::new(&format!("_f{i}"), Span::call_site());
293                    let t = &field.ty;
294                    (i.to_string(), x, t)
295                })
296                .collect();
297            let xs = fields.iter().map(|(_, x, _)| x);
298            (quote! { #tag ( #(#xs),* ) }, fields)
299        }
300        Fields::Unit => (quote! { #tag }, vec![]),
301    }
302}
303
304fn parse_at(validated: bool, fields: &ProcessedFields, constructor: &TokenStream2) -> TokenStream2 {
305    let steps: Vec<_> = fields
306        .iter()
307        .map(|(_, x, t)| {
308            quote! {
309                let (#x, pos) = <#t as ::tygr::Grammar>::parse_at(input, pos, state.reborrow())?;
310            }
311        })
312        .collect();
313    let (start_pos, validate) = if validated {
314        let trace = if cfg!(feature = "trace") {
315            quote! {
316                state.expect(pos, ::tygr::Expectation::Valid {
317                    node: Self::NAME,
318                    text: input[start_pos..pos].to_string(),
319                    requirement: <Self as ::tygr::Validate>::REQUIREMENT,
320                });
321            }
322        } else if cfg!(feature = "trace_pos") {
323            quote! {
324                state.expect(pos);
325            }
326        } else {
327            quote! {}
328        };
329        (
330            quote! { let start_pos = pos; },
331            quote! {
332                if !::tygr::Validate::validate(&value) {
333                    #trace
334                    return None
335                }
336            },
337        )
338    } else {
339        (quote! {}, quote! {})
340    };
341    quote! {
342        #start_pos
343        #(#steps)*
344        let value = #constructor;
345        #validate
346        Some((value, pos))
347    }
348}
349
350fn scan_at(validated: bool, fields: &ProcessedFields, constructor: &TokenStream2) -> TokenStream2 {
351    if validated {
352        let parse_at = parse_at(true, fields, constructor);
353        quote! { ({#parse_at}).map(|(_, pos)| pos) }
354    } else {
355        let steps: Vec<_> = fields
356            .iter()
357            .map(|(_, _, t)| {
358                quote! {
359                    let pos = <#t as ::tygr::Grammar>::scan_at(input, pos, state.reborrow())?;
360                }
361            })
362            .collect();
363        quote! {
364            #(#steps)*
365            Some(pos)
366        }
367    }
368}
369
370fn print_steps(fields: &ProcessedFields) -> Vec<TokenStream2> {
371    fields
372        .iter()
373        .map(|(_, x, _)| {
374            quote! {
375                ::tygr::Grammar::print_to(#x, buf);
376            }
377        })
378        .collect()
379}
380
381fn to_bnf(fields: &ProcessedFields) -> TokenStream2 {
382    let ts = fields.iter().map(|(_, _, t)| quote! { #t });
383    quote! {
384        ::tygr::bnf::Expr::sequence(vec![
385            #(<#ts as ::tygr::Grammar>::to_bnf()),*
386        ])
387    }
388}
389
390/// `A::fail_at(..) || B::fail_at(..) || .. || false`: each field reports itself
391/// unconditionally; a required (non-nullable) field short-circuits the rest,
392/// since real parsing would never reach them either.
393fn fail_at(fields: &ProcessedFields) -> TokenStream2 {
394    let calls = fields.iter().map(|(_, _, t)| {
395        quote! { <#t as ::tygr::Grammar>::fail_at(pos, state.reborrow()) }
396    });
397    quote! {
398        #(#calls ||)* false
399    }
400}
401
402fn bnf_ref(grammar_name: &str, hidden: bool, to_bnf: &TokenStream2, inline: bool) -> TokenStream2 {
403    if hidden {
404        quote! { ::tygr::bnf::Expr::empty() }
405    } else if inline {
406        quote! { #to_bnf }
407    } else {
408        quote! { ::tygr::bnf::Expr::RuleRef(#grammar_name.to_string()) }
409    }
410}
411
412struct FieldsInfo {
413    constructor: TokenStream2,
414    parse_at: TokenStream2,
415    scan_at: TokenStream2,
416    print_to: TokenStream2,
417    to_bnf: TokenStream2,
418    fail_at: TokenStream2,
419    first: TokenStream2,
420}
421
422impl FieldsInfo {
423    fn from(validated: bool, tag: Tag, fields: &Fields) -> Self {
424        let (constructor, fields) = components_and_xts(tag.as_constructor(), fields);
425        let print_steps = print_steps(&fields);
426        let parse_at = parse_at(validated, &fields, &constructor);
427        let scan_at = scan_at(validated, &fields, &constructor);
428        let mut first = quote! { ::tygr::first::OptionalFirst<::tygr::first::Never> };
429        for (_, _, t) in &fields {
430            first = quote! {
431                <#first as ::tygr::first::First>::Concat<#t>
432            }
433        }
434        Self {
435            parse_at: quote! { #parse_at },
436            scan_at: quote! { #scan_at },
437            print_to: quote! {#(#print_steps)*},
438            to_bnf: to_bnf(&fields),
439            fail_at: fail_at(&fields),
440            constructor,
441            first,
442        }
443    }
444}
445
446fn impl_convert(input: &DeriveInput, convert: Convert) -> syn::Result<TokenStream2> {
447    let ident = &input.ident;
448    let generics = &input.generics;
449    let GrammarAttr {
450        name,
451        hidden,
452        inline,
453        validated,
454    } = grammar_attr(input)?;
455    let name = name.unwrap_or_else(|| default_name(ident));
456    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
457    let self_ty = quote! { #ident #ty_generics };
458    let source = quote! { <#self_ty as ::tygr::GrammarFrom>::Source };
459    // parse_at: parse Source, then build Self via the forward conversion.
460    let trace = if cfg!(feature = "trace") {
461        quote! {
462            state.expect(
463                end,
464                ::tygr::Expectation::GrammarFrom {
465                    from: input[pos..end].to_string(),
466                    into: Self::NAME,
467                    fail: ::std::string::ToString::to_string(&err),
468                },
469            );
470        }
471    } else if cfg!(feature = "trace_pos") {
472        quote! {
473            state.expect(end);
474        }
475    } else {
476        quote! {}
477    };
478    // After the conversion succeeds, `#[grammar(validated)]` can still
479    // reject `value` — independent of whether the conversion itself can fail.
480    let validate = if validated {
481        let validate_trace = if cfg!(feature = "trace") {
482            quote! {
483                state.expect(end, ::tygr::Expectation::Valid {
484                    node: Self::NAME,
485                    text: input[pos..end].to_string(),
486                    requirement: <Self as ::tygr::Validate>::REQUIREMENT,
487                });
488            }
489        } else if cfg!(feature = "trace_pos") {
490            quote! {
491                state.expect(end);
492            }
493        } else {
494            quote! {}
495        };
496        quote! {
497            if !::tygr::Validate::validate(&value) {
498                #validate_trace
499                return None
500            }
501        }
502    } else {
503        quote! {}
504    };
505    let parse_at = match convert {
506        Convert::FromStr => quote! {
507            let end = <#source as ::tygr::Grammar>::scan_at(input, pos, state.reborrow())?;
508            match <#self_ty as ::core::str::FromStr>::from_str(&input[pos..end]) {
509                Ok(value) => {
510                    #validate
511                    Some((value, end))
512                }
513                Err(err) => {
514                    #trace
515                    let _ = &err;
516                    None
517                }
518            }
519        },
520        Convert::From => quote! {
521            let (source, end) = <#source as ::tygr::Grammar>::parse_at(input, pos, state.reborrow())?;
522            let value = <#self_ty as ::core::convert::From<#source>>::from(source);
523            #validate
524            Some((value, end))
525        },
526        Convert::TryFrom => quote! {
527            let (source, end) = <#source as ::tygr::Grammar>::parse_at(input, pos, state.reborrow())?;
528            match <#self_ty as ::core::convert::TryFrom<#source>>::try_from(source) {
529                Ok(value) => {
530                    #validate
531                    Some((value, end))
532                }
533                Err(err) => {
534                    #trace
535                    let _ = &err;
536                    None
537                }
538            }
539        },
540    };
541    // scan_at: infallible `From` alone can just delegate to `Source`; anything
542    // that can still reject (a fallible conversion, or `validated`) has to
543    // run the full check via `parse_at`.
544    let scan_at = if !validated && matches!(convert, Convert::From) {
545        quote! {
546            <#source as ::tygr::Grammar>::scan_at(input, pos, state)
547        }
548    } else {
549        quote! {
550            Self::parse_at(input, pos, state).map(|(_, end)| end)
551        }
552    };
553    let to_bnf = quote! { <#source as ::tygr::Grammar>::to_bnf() };
554    let bnf_ref = bnf_ref(&name, hidden, &to_bnf, inline);
555    // The rule's own definition shows the side-condition; a reference to it
556    // from elsewhere (`RuleRef`, or a spliced `#[grammar(inline)]` body)
557    // doesn't — nesting `^N` inside another rule's sequence would make its
558    // scope ambiguous. The conversion's own side-condition is BNF-only (the
559    // trace already has the real conversion error's message); `validated`'s
560    // is also traced, same as for `#[derive(Grammar)]`.
561    let to_bnf_def = match convert {
562        Convert::From => to_bnf,
563        Convert::FromStr | Convert::TryFrom => quote! {
564            ::tygr::bnf::Expr::side_condition(#to_bnf, "be convertible")
565        },
566    };
567    let to_bnf_def = if validated {
568        quote! { ::tygr::bnf::Expr::side_condition(#to_bnf_def, <Self as ::tygr::Validate>::REQUIREMENT) }
569    } else {
570        to_bnf_def
571    };
572    Ok(quote! {
573        impl #impl_generics ::tygr::Grammar for #self_ty #where_clause {
574            type First = <#source as ::tygr::Grammar>::First;
575
576            #[inline]
577            fn parse_at(input: &str, pos: usize, #[allow(unused_mut)] mut state: ::tygr::State) -> Option<(Self, usize)> {
578                #parse_at
579            }
580
581            #[inline]
582            fn scan_at(input: &str, pos: usize, #[allow(unused_mut)] mut state: ::tygr::State) -> Option<usize> {
583                #scan_at
584            }
585
586            fn print_to(&self, buf: &mut ::std::string::String) {
587                <#self_ty as ::tygr::GrammarFrom>::print_to(self, buf);
588            }
589
590            fn to_bnf() -> ::tygr::bnf::Expr {
591                #bnf_ref
592            }
593
594            fn fail_at(pos: usize, state: ::tygr::State) -> bool {
595                <#source as ::tygr::Grammar>::fail_at(pos, state)
596            }
597        }
598
599        impl #impl_generics ::tygr::GrammarRule for #self_ty #where_clause {
600            const NAME: &'static str = #name;
601
602            fn to_bnf_def() -> ::tygr::bnf::Expr {
603                #to_bnf_def
604            }
605        }
606    })
607}
608
609fn impl_struct(
610    ident: &Ident,
611    generics: &Generics,
612    name: String,
613    hidden: bool,
614    inline: bool,
615    validated: bool,
616    data: &DataStruct,
617) -> syn::Result<TokenStream2> {
618    let tag = Tag::new(quote! { #ident }, None);
619    let fields = &data.fields;
620    let FieldsInfo {
621        constructor,
622        parse_at,
623        scan_at,
624        print_to,
625        to_bnf,
626        fail_at,
627        first,
628    } = FieldsInfo::from(validated, tag, fields);
629    let parse_at = with_node(inline, parse_at);
630    let scan_at = with_node(inline, scan_at);
631    let fail_at = with_node(inline, fail_at);
632    let to_bnf = if validated {
633        quote! { ::tygr::bnf::Expr::side_condition(#to_bnf, <Self as ::tygr::Validate>::REQUIREMENT) }
634    } else {
635        to_bnf
636    };
637    let bnf_ref = bnf_ref(&name, hidden, &to_bnf, inline);
638    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
639    Ok(quote! {
640        impl #impl_generics ::tygr::Grammar for #ident #ty_generics #where_clause {
641            type First = #first;
642
643            #[inline]
644            fn parse_at(input: &str, pos: usize, #[allow(unused_mut)] mut state: ::tygr::State) -> Option<(Self, usize)> {
645                #parse_at
646            }
647
648            #[inline]
649            fn scan_at(input: &str, pos: usize, #[allow(unused_mut)] mut state: ::tygr::State) -> Option<usize> {
650                #scan_at
651            }
652
653            fn print_to(&self, buf: &mut ::std::string::String) {
654                let #constructor = &self;
655                #print_to
656            }
657
658            fn to_bnf() -> ::tygr::bnf::Expr {
659                #bnf_ref
660            }
661
662            fn fail_at(#[allow(unused_variables)] pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> bool {
663                #fail_at
664            }
665        }
666
667        impl #impl_generics ::tygr::GrammarRule for #ident #ty_generics #where_clause {
668            const NAME: &'static str = #name;
669
670            fn to_bnf_def() -> ::tygr::bnf::Expr {
671                #to_bnf
672            }
673        }
674    })
675}
676
677// ── Enum → alternation ──────────────────────────────────────────────────────
678
679fn impl_enum(
680    ident: &Ident,
681    generics: &Generics,
682    name: String,
683    hidden: bool,
684    inline: bool,
685    validated: bool,
686    data: &DataEnum,
687) -> syn::Result<TokenStream2> {
688    let mut each_constructor = vec![];
689    let mut each_parse_at = vec![];
690    let mut each_scan_at = vec![];
691    let mut each_print_to = vec![];
692    let mut each_to_bnf = vec![];
693    let mut each_fail_at = vec![];
694    let mut each_first = vec![];
695    for variant in &data.variants {
696        let variant_ident = &variant.ident;
697        let tag = Tag::new(quote! { #ident }, Some(quote! {#variant_ident}));
698        let FieldsInfo {
699            constructor,
700            parse_at,
701            scan_at,
702            print_to,
703            to_bnf,
704            fail_at,
705            first,
706        } = FieldsInfo::from(validated, tag, &variant.fields);
707        each_constructor.push(constructor);
708        each_parse_at.push(parse_at);
709        each_scan_at.push(scan_at);
710        each_print_to.push(print_to);
711        each_to_bnf.push(to_bnf);
712        each_fail_at.push(fail_at);
713        each_first.push(first);
714    }
715    // Alternatives aren't sequential: every variant reports itself unconditionally
716    // (no short-circuiting between them). The enum as a whole is required only if
717    // every variant is (`&`, not `&&`, so all still get called for their side effects).
718    let fail_at_variants_body = {
719        let mut variants = each_fail_at.iter();
720        let first = variants.next().cloned().unwrap_or_else(|| quote! { false });
721        variants.fold(first, |acc, next| quote! { (#acc) & (#next) })
722    };
723    let to_bnf = quote! { ::tygr::bnf::Expr::alternation(vec![ #(#each_to_bnf),* ]) };
724    let to_bnf = if validated {
725        quote! { ::tygr::bnf::Expr::side_condition(#to_bnf, <Self as ::tygr::Validate>::REQUIREMENT) }
726    } else {
727        to_bnf
728    };
729    let bnf_ref = bnf_ref(&name, hidden, &to_bnf, inline);
730    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
731    let n = each_first.len();
732    let self_ty = quote! { #ident #ty_generics };
733    let parse_fn_ty = quote! {
734        fn(&str, usize, ::tygr::State<'_>) -> Option<(#self_ty, usize)>
735    };
736    let scan_fn_ty = quote! {
737        fn(&str, usize, ::tygr::State<'_>) -> Option<usize>
738    };
739    let case_fns: Vec<Ident> = (0..n)
740        .map(|k| Ident::new(&format!("parse_case_{k}"), Span::call_site()))
741        .collect();
742    let scan_fns: Vec<Ident> = (0..n)
743        .map(|k| Ident::new(&format!("scan_case_{k}"), Span::call_site()))
744        .collect();
745    let case_defs = case_fns.iter().zip(&each_parse_at).enumerate().map(|(k, (name, arm))| {
746        let next = if k + 1 < n {
747            let nx = &case_fns[k + 1];
748            quote! { Self::#nx(input, pos, state) }
749        } else {
750            quote! { None }
751        };
752        quote! {
753            #[inline]
754            fn #name(input: &str, pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> Option<(Self, usize)> {
755                if let Some(result) = (|| { #arm })() { return Some(result); }
756                #next
757            }
758        }
759    });
760    let scan_case_defs = scan_fns
761        .iter()
762        .zip(&each_scan_at)
763        .enumerate()
764        .map(|(k, (name, arm))| {
765            let next = if k + 1 < n {
766                let nx = &scan_fns[k + 1];
767                quote! { Self::#nx(input, pos, state) }
768            } else {
769                quote! { None }
770            };
771            quote! {
772                #[inline]
773                fn #name(input: &str, pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> Option<usize> {
774                    if let Some(result) = (|| { #arm })() { return Some(result); }
775                    #next
776                }
777            }
778        });
779    // slots 0-255: u8 / byte / ascii char
780    // slots 256  : EOF
781    let build_table = |fn_ty: &TokenStream2, fns: &[Ident], miss: &TokenStream2| {
782        quote! {{
783            let parsers: [#fn_ty; #n] = [ #(<#self_ty>::#fns),* ];
784            // No case's FIRST set contains this byte, so none can match: dispatch
785            // straight to `miss`, which reports every variant's expectation in
786            // O(1) without attempting any of their (guaranteed-failing) real parses.
787            let mut table: [#fn_ty; 257] = [#miss; 257];
788            let mut first = 0usize;
789            while first < 257 {
790                let mut case = 0usize;
791                while case < #n {
792                    if CONTAINS_NIL[case] || (first <= 255 && CONTAINS_BYTE[case][first]) {
793                        table[first] = parsers[case];
794                        break;
795                    }
796                    case += 1;
797                }
798                first += 1;
799            }
800            table
801        }}
802    };
803    let parse_table = build_table(
804        &parse_fn_ty,
805        &case_fns,
806        &quote! { <#self_ty>::parse_case_miss },
807    );
808    let scan_table = build_table(
809        &scan_fn_ty,
810        &scan_fns,
811        &quote! { <#self_ty>::scan_case_miss },
812    );
813    const DISPATCH_THRESHOLD: usize = 4;
814    // `cfg!`, not `#[cfg(...)]`: this call is spliced into whatever crate
815    // derives `Grammar`, so an embedded `#[cfg(feature = "trace_pos")]`
816    // would check that crate's own (nonexistent) feature, not tygr's.
817    let fail_at_on_miss = if cfg!(feature = "trace_pos") {
818        quote! { Self::fail_at_variants(pos, state); }
819    } else {
820        quote! {}
821    };
822    let (dispatch_body, scan_dispatch_body, case_impls) = if n >= DISPATCH_THRESHOLD {
823        (
824            quote! {
825                const CONTAINS_NIL: [bool; #n] = [ #(<#each_first as ::tygr::first::First>::CONTAINS_NIL),* ];
826                const CONTAINS_BYTE: [[bool; 256]; #n] = [ #(<#each_first as ::tygr::first::First>::CONTAINS_BYTE),* ];
827                const DISPATCH: [#parse_fn_ty; 257] = #parse_table;
828                let first = input.as_bytes().get(pos).map(|&first| first as usize).unwrap_or(256);
829                DISPATCH[first](input, pos, state)
830            },
831            quote! {
832                const CONTAINS_NIL: [bool; #n] = [ #(<#each_first as ::tygr::first::First>::CONTAINS_NIL),* ];
833                const CONTAINS_BYTE: [[bool; 256]; #n] = [ #(<#each_first as ::tygr::first::First>::CONTAINS_BYTE),* ];
834                const DISPATCH: [#scan_fn_ty; 257] = #scan_table;
835                let first = input.as_bytes().get(pos).map(|&first| first as usize).unwrap_or(256);
836                DISPATCH[first](input, pos, state)
837            },
838            quote! {
839                #(#case_defs)*
840                #(#scan_case_defs)*
841
842                #[inline]
843                fn parse_case_miss(_input: &str, #[allow(unused_variables)] pos: usize, #[allow(unused_variables)] state: ::tygr::State) -> Option<(Self, usize)> {
844                    #fail_at_on_miss
845                    None
846                }
847
848                #[inline]
849                fn scan_case_miss(_input: &str, #[allow(unused_variables)] pos: usize, #[allow(unused_variables)] state: ::tygr::State) -> Option<usize> {
850                    #fail_at_on_miss
851                    None
852                }
853            },
854        )
855    } else {
856        (
857            quote! {
858                #( if let Some(result) = (|| { #each_parse_at })() { return Some(result); } )*
859                None
860            },
861            quote! {
862                #( if let Some(result) = (|| { #each_scan_at })() { return Some(result); } )*
863                None
864            },
865            quote! {},
866        )
867    };
868    let parse_body = with_node(inline, dispatch_body);
869    let scan_body = with_node(inline, scan_dispatch_body);
870    let fail_at = with_node(inline, quote! { Self::fail_at_variants(pos, state) });
871    let first = {
872        let mut the_first = quote! { ::tygr::first::Never };
873        for first in each_first {
874            the_first = quote! { <#the_first as ::tygr::first::First>::Union<#first> };
875        }
876        the_first
877    };
878    Ok(quote! {
879        impl #impl_generics #ident #ty_generics #where_clause {
880            #case_impls
881
882            #[inline]
883            fn parse_case_none(_input: &str, _pos: usize, _state: ::tygr::State) -> Option<(Self, usize)> {
884                None
885            }
886
887            #[inline]
888            fn fail_at_variants(#[allow(unused_variables)] pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> bool {
889                #fail_at_variants_body
890            }
891        }
892
893        impl #impl_generics ::tygr::Grammar for #ident #ty_generics #where_clause {
894            type First = #first;
895
896            #[inline]
897            fn parse_at(input: &str, pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> Option<(Self, usize)> {
898                #parse_body
899            }
900
901            #[inline]
902            fn scan_at(input: &str, pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> Option<usize> {
903                #scan_body
904            }
905
906            fn print_to(&self, buf: &mut ::std::string::String) {
907                match self {
908                    #(#each_constructor => { #each_print_to }),*
909                }
910            }
911
912            fn to_bnf() -> ::tygr::bnf::Expr {
913                #bnf_ref
914            }
915
916            fn fail_at(pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> bool {
917                #fail_at
918            }
919        }
920
921        impl #impl_generics ::tygr::GrammarRule for #ident #ty_generics #where_clause {
922            const NAME: &'static str = #name;
923
924            fn to_bnf_def() -> ::tygr::bnf::Expr {
925                #to_bnf
926            }
927        }
928    })
929}