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#[proc_macro_derive(Grammar, attributes(grammar))]
7pub fn derive_grammar(input: TokenStream) -> TokenStream {
8    let mut input = syn::parse_macro_input!(input as DeriveInput);
9    for param in input.generics.type_params_mut() {
10        param.bounds.push(parse_quote!(::tygr::Grammar));
11    }
12    match impl_grammar(&input) {
13        Ok(tokens) => tokens.into(),
14        Err(err) => err.to_compile_error().into(),
15    }
16}
17
18/// `Grammar` via `GrammarFrom` + `FromStr` on the matched text (`Err` is traced).
19#[proc_macro_derive(GrammarFromStr)]
20pub fn derive_grammar_from_str(input: TokenStream) -> TokenStream {
21    derive_convert(input, Convert::FromStr)
22}
23
24/// `Grammar` via `GrammarFrom` + `From<Source>`.
25#[proc_macro_derive(GrammarFromOther)]
26pub fn derive_grammar_from_source(input: TokenStream) -> TokenStream {
27    derive_convert(input, Convert::From)
28}
29
30/// `Grammar` via `GrammarFrom` + `TryFrom<Source>` (`Error` is traced on rejection).
31#[proc_macro_derive(GrammarTryFromOther)]
32pub fn derive_grammar_try_from_source(input: TokenStream) -> TokenStream {
33    derive_convert(input, Convert::TryFrom)
34}
35
36#[derive(Clone, Copy)]
37enum Convert {
38    FromStr,
39    From,
40    TryFrom,
41}
42
43fn derive_convert(input: TokenStream, convert: Convert) -> TokenStream {
44    let mut input = syn::parse_macro_input!(input as DeriveInput);
45    for param in input.generics.type_params_mut() {
46        param.bounds.push(parse_quote!(::tygr::Grammar));
47    }
48    match impl_convert(&input, convert) {
49        Ok(tokens) => tokens.into(),
50        Err(err) => err.to_compile_error().into(),
51    }
52}
53
54/// Expands a string literal into a type-level literal token.
55///
56/// This is the **primary way** to create literal tokens — the underlying
57/// `StringEq<CharThen<CH, T>>` type is an implementation detail and should not
58/// be named directly.
59///
60/// - Single character: `StringEq!(",")` → `StringEq<CharThen<',', ()>>`
61/// - Multiple characters: `StringEq!("->")` → `StringEq<CharThen<'-', CharThen<'>', ()>>>`
62#[proc_macro]
63#[allow(non_snake_case)]
64pub fn StringEq(input: TokenStream) -> TokenStream {
65    let lit = syn::parse_macro_input!(input as syn::LitStr);
66    let value = lit.value();
67    let chars: Vec<char> = value.chars().collect();
68
69    if chars.is_empty() {
70        return syn::Error::new(
71            lit.span(),
72            "StringEq!() requires a non-empty string literal",
73        )
74        .to_compile_error()
75        .into();
76    }
77
78    let chain = build_nested_chain(&chars, |ch, rest| quote! { ::tygr::CharThen<#ch, #rest> });
79    quote! { ::tygr::StringEq<#chain> }.into()
80}
81
82/// Build a right-nested chain of literal-token types terminated by `()`.
83fn build_nested_chain<F>(items: &[char], mapper: F) -> TokenStream2
84where
85    F: Fn(&char, TokenStream2) -> TokenStream2,
86{
87    items
88        .iter()
89        .rev()
90        .fold(quote! { () }, |rest, ch| mapper(ch, rest))
91}
92
93#[proc_macro]
94#[allow(non_snake_case)]
95pub fn StringEqCI(input: TokenStream) -> TokenStream {
96    let lit = syn::parse_macro_input!(input as syn::LitStr);
97    let value = lit.value();
98    let chars: Vec<char> = value.chars().collect();
99
100    if chars.is_empty() {
101        return syn::Error::new(
102            lit.span(),
103            "StringEqCI!() requires a non-empty string literal",
104        )
105        .to_compile_error()
106        .into();
107    }
108
109    let chain = build_nested_chain(&chars, |ch, rest| quote! { ::tygr::CharCIThen<#ch, #rest> });
110    quote! { ::tygr::StringEqCI<#chain> }.into()
111}
112
113#[derive(Clone)]
114struct Tag {
115    name: TokenStream2,
116    case: Option<TokenStream2>,
117}
118
119impl Tag {
120    fn new(name: TokenStream2, case: Option<TokenStream2>) -> Self {
121        Self { name, case }
122    }
123
124    fn as_constructor(&self) -> TokenStream2 {
125        let name = &self.name;
126        let case = if let Some(case) = &self.case {
127            quote! { :: #case }
128        } else {
129            quote! {}
130        };
131        quote! { #name #case }
132    }
133}
134
135fn with_node(transparent: bool, body: TokenStream2) -> TokenStream2 {
136    if transparent {
137        body
138    } else {
139        quote! {
140            let mut state = state.node(Self::NAME, pos);
141            #body
142        }
143    }
144}
145
146fn impl_grammar(input: &DeriveInput) -> syn::Result<TokenStream2> {
147    let ident = &input.ident;
148    let generics = &input.generics;
149    let GrammarAttr {
150        name,
151        hidden,
152        transparent,
153        filtered,
154    } = grammar_attr(input);
155    let name = name.unwrap_or_else(|| {
156        let ident = ident.to_string();
157        #[cfg(feature = "lower_bnf_name")]
158        let ident = ident.to_ascii_lowercase();
159        #[cfg(feature = "upper_bnf_name")]
160        let ident = ident.to_ascii_uppercase();
161        ident
162    });
163    match &input.data {
164        Data::Struct(data) => {
165            impl_struct(ident, generics, name, hidden, transparent, filtered, data)
166        }
167        Data::Enum(data) => impl_enum(ident, generics, name, hidden, transparent, filtered, data),
168        Data::Union(_) => Err(syn::Error::new_spanned(
169            ident,
170            "Grammar cannot be derived for unions",
171        )),
172    }
173}
174
175fn grammar_attr(input: &DeriveInput) -> GrammarAttr {
176    input
177        .attrs
178        .iter()
179        .filter_map(|attr| {
180            if attr.path().is_ident("grammar") {
181                attr.parse_args::<GrammarAttr>().ok()
182            } else {
183                None
184            }
185        })
186        .next()
187        .unwrap_or_default()
188}
189
190#[derive(Default)]
191struct GrammarAttr {
192    name: Option<String>,
193    hidden: bool,
194    transparent: bool,
195    filtered: bool,
196}
197
198impl syn::parse::Parse for GrammarAttr {
199    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
200        let mut attr = GrammarAttr::default();
201
202        while !input.is_empty() {
203            let ident: syn::Ident = input.parse()?;
204            if ident == "name" {
205                let _: syn::Token![=] = input.parse()?;
206                let lit: syn::LitStr = input.parse()?;
207                attr.name = Some(lit.value());
208            } else if ident == "hidden" {
209                attr.hidden = true;
210                // Hidden nodes are not presented in BNF so they must be transparent
211                // as non-transparent node names are presented in traces
212                attr.transparent = true;
213            } else if ident == "transparent" {
214                attr.transparent = true;
215            } else if ident == "filtered" {
216                attr.filtered = true;
217            } else {
218                return Err(syn::Error::new(
219                    ident.span(),
220                    "expected `name`, `hidden`, `transparent`, or `filtered`",
221                ));
222            }
223            if input.is_empty() {
224                break;
225            }
226            let _: syn::Token![,] = input.parse()?;
227        }
228
229        Ok(attr)
230    }
231}
232
233type ProcessedFields<'a> = Vec<(String, Ident, &'a syn::Type)>;
234
235fn components_and_xts(tag: TokenStream2, fields: &Fields) -> (TokenStream2, ProcessedFields<'_>) {
236    match fields {
237        Fields::Named(fields) => {
238            let fields: Vec<_> = fields
239                .named
240                .iter()
241                .map(|field| {
242                    let x = field.ident.as_ref().unwrap().clone();
243                    let t = &field.ty;
244                    (x.to_string(), x, t)
245                })
246                .collect();
247            let xs = fields.iter().map(|(_, x, _)| x);
248            (quote! { #tag { #(#xs),* } }, fields)
249        }
250        Fields::Unnamed(fields) => {
251            let fields: Vec<_> = fields
252                .unnamed
253                .iter()
254                .enumerate()
255                .map(|(i, field)| {
256                    let x = Ident::new(&format!("_f{i}"), Span::call_site());
257                    let t = &field.ty;
258                    (i.to_string(), x, t)
259                })
260                .collect();
261            let xs = fields.iter().map(|(_, x, _)| x);
262            (quote! { #tag ( #(#xs),* ) }, fields)
263        }
264        Fields::Unit => (quote! { #tag }, vec![]),
265    }
266}
267
268fn parse_at(filtered: bool, fields: &ProcessedFields, constructor: &TokenStream2) -> TokenStream2 {
269    let steps: Vec<_> = fields
270        .iter()
271        .map(|(_, x, t)| {
272            quote! {
273                let (#x, pos) = <#t as ::tygr::Grammar>::parse_at(input, pos, state.reborrow())?;
274            }
275        })
276        .collect();
277    let filter = if filtered {
278        quote! {
279            if let Some(be_valid) = ::tygr::FilterResult::be_valid(value.filter()) {
280                return None
281            }
282        }
283    } else {
284        quote! {}
285    };
286    quote! {
287        #(#steps)*
288        let value = #constructor;
289        #filter
290        Some((value, pos))
291    }
292}
293
294fn scan_at(filtered: bool, fields: &ProcessedFields, constructor: &TokenStream2) -> TokenStream2 {
295    if filtered {
296        let parse_at = parse_at(true, fields, constructor);
297        quote! { ({#parse_at}).map(|(_, pos)| pos) }
298    } else {
299        let steps: Vec<_> = fields
300            .iter()
301            .map(|(_, _, t)| {
302                quote! {
303                    let pos = <#t as ::tygr::Grammar>::scan_at(input, pos, state.reborrow())?;
304                }
305            })
306            .collect();
307        quote! {
308            #(#steps)*
309            Some(pos)
310        }
311    }
312}
313
314fn print_steps(fields: &ProcessedFields) -> Vec<TokenStream2> {
315    fields
316        .iter()
317        .map(|(_, x, _)| {
318            quote! {
319                ::tygr::Grammar::print_to(#x, buf);
320            }
321        })
322        .collect()
323}
324
325fn to_bnf(fields: &ProcessedFields) -> TokenStream2 {
326    let ts = fields.iter().map(|(_, _, t)| quote! { #t });
327    quote! {
328        ::tygr::bnf::Expr::sequence(vec![
329            #(<#ts as ::tygr::Grammar>::to_bnf()),*
330        ])
331    }
332}
333
334fn bnf_ref(
335    grammar_name: &str,
336    hidden: bool,
337    to_bnf: TokenStream2,
338    transparent: bool,
339) -> TokenStream2 {
340    if hidden {
341        quote! { ::tygr::bnf::Expr::Empty }
342    } else if transparent {
343        to_bnf
344    } else {
345        quote! { ::tygr::bnf::Expr::RuleRef(#grammar_name.to_string()) }
346    }
347}
348
349struct FieldsInfo {
350    constructor: TokenStream2,
351    parse_at: TokenStream2,
352    scan_at: TokenStream2,
353    print_to: TokenStream2,
354    to_bnf: TokenStream2,
355    first: TokenStream2,
356}
357
358impl FieldsInfo {
359    fn from(filtered: bool, tag: Tag, fields: &Fields) -> Self {
360        let (constructor, fields) = components_and_xts(tag.as_constructor(), fields);
361        let print_steps = print_steps(&fields);
362        let parse_at = parse_at(filtered, &fields, &constructor);
363        let scan_at = scan_at(filtered, &fields, &constructor);
364        let mut first = quote! { ::tygr::EmptyFirst };
365        for (_, _, t) in &fields {
366            first = quote! {
367                <#first as ::tygr::First>::Concat<#t>
368            }
369        }
370        Self {
371            parse_at: quote! { #parse_at },
372            scan_at: quote! { #scan_at },
373            print_to: quote! {#(#print_steps)*},
374            to_bnf: to_bnf(&fields),
375            constructor,
376            first,
377        }
378    }
379}
380
381fn impl_convert(input: &DeriveInput, convert: Convert) -> syn::Result<TokenStream2> {
382    let ident = &input.ident;
383    let generics = &input.generics;
384    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
385    let self_ty = quote! { #ident #ty_generics };
386    let source = quote! { <#self_ty as ::tygr::GrammarFrom>::Source };
387    // parse_at: parse Source, then build Self via the forward conversion.
388    let trace = if cfg!(feature = "trace_attempts") {
389        quote! {
390            state.expect(
391                pos,
392                ::tygr::Expectation::Conversion(::std::string::ToString::to_string(&err)),
393            );
394        }
395    } else if cfg!(feature = "trace_pos") {
396        quote! {
397            state.expect(pos);
398        }
399    } else {
400        quote! {}
401    };
402    let parse_at = match convert {
403        Convert::FromStr => quote! {
404            let end = <#source as ::tygr::Grammar>::scan_at(input, pos, state.reborrow())?;
405            match <#self_ty as ::core::str::FromStr>::from_str(&input[pos..end]) {
406                Ok(value) => Some((value, end)),
407                Err(err) => {
408                    #trace
409                    let _ = &err;
410                    None
411                }
412            }
413        },
414        Convert::From => quote! {
415            let (source, end) = <#source as ::tygr::Grammar>::parse_at(input, pos, state.reborrow())?;
416            Some((<#self_ty as ::core::convert::From<#source>>::from(source), end))
417        },
418        Convert::TryFrom => quote! {
419            let (source, end) = <#source as ::tygr::Grammar>::parse_at(input, pos, state.reborrow())?;
420            match <#self_ty as ::core::convert::TryFrom<#source>>::try_from(source) {
421                Ok(value) => Some((value, end)),
422                Err(err) => {
423                    #trace
424                    let _ = &err;
425                    None
426                }
427            }
428        },
429    };
430    // scan_at: `From` can't fail beyond Source; `FromStr`/`TryFrom` run the check.
431    let scan_at = match convert {
432        Convert::From => quote! {
433            <#source as ::tygr::Grammar>::scan_at(input, pos, state)
434        },
435        Convert::FromStr | Convert::TryFrom => quote! {
436            Self::parse_at(input, pos, state).map(|(_, end)| end)
437        },
438    };
439    Ok(quote! {
440        impl #impl_generics ::tygr::Grammar for #self_ty #where_clause {
441            type First = <#source as ::tygr::Grammar>::First;
442
443            #[inline]
444            fn parse_at(input: &str, pos: usize, #[allow(unused_mut)] mut state: ::tygr::State) -> Option<(Self, usize)> {
445                #parse_at
446            }
447
448            #[inline]
449            fn scan_at(input: &str, pos: usize, #[allow(unused_mut)] mut state: ::tygr::State) -> Option<usize> {
450                #scan_at
451            }
452
453            fn print_to(&self, buf: &mut ::std::string::String) {
454                <#self_ty as ::tygr::GrammarFrom>::print_to(self, buf);
455            }
456
457            fn to_bnf() -> ::tygr::bnf::Expr {
458                <#source as ::tygr::Grammar>::to_bnf()
459            }
460        }
461    })
462}
463
464fn impl_struct(
465    ident: &Ident,
466    generics: &Generics,
467    name: String,
468    hidden: bool,
469    transparent: bool,
470    filtered: bool,
471    data: &DataStruct,
472) -> syn::Result<TokenStream2> {
473    let tag = Tag::new(quote! { #ident }, None);
474    let fields = &data.fields;
475    let FieldsInfo {
476        constructor,
477        parse_at,
478        scan_at,
479        print_to,
480        to_bnf,
481        first,
482    } = FieldsInfo::from(filtered, tag, fields);
483    let parse_at = with_node(transparent, parse_at);
484    let scan_at = with_node(transparent, scan_at);
485    let bnf_ref = bnf_ref(&name, hidden, to_bnf.clone(), transparent);
486    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
487    Ok(quote! {
488        impl #impl_generics ::tygr::Grammar for #ident #ty_generics #where_clause {
489            type First = #first;
490
491            #[inline]
492            fn parse_at(input: &str, pos: usize, #[allow(unused_mut)] mut state: ::tygr::State) -> Option<(Self, usize)> {
493                #parse_at
494            }
495
496            #[inline]
497            fn scan_at(input: &str, pos: usize, #[allow(unused_mut)] mut state: ::tygr::State) -> Option<usize> {
498                #scan_at
499            }
500
501            fn print_to(&self, buf: &mut ::std::string::String) {
502                let #constructor = &self;
503                #print_to
504            }
505
506            fn to_bnf() -> ::tygr::bnf::Expr {
507                #bnf_ref
508            }
509        }
510
511        impl #impl_generics ::tygr::GrammarRule for #ident #ty_generics #where_clause {
512            const NAME: &'static str = #name;
513
514            fn to_bnf_def() -> ::tygr::bnf::Expr {
515                #to_bnf
516            }
517        }
518    })
519}
520
521// ── Enum → alternation ──────────────────────────────────────────────────────
522
523fn impl_enum(
524    ident: &Ident,
525    generics: &Generics,
526    name: String,
527    hidden: bool,
528    transparent: bool,
529    filtered: bool,
530    data: &DataEnum,
531) -> syn::Result<TokenStream2> {
532    let mut each_constructor = vec![];
533    let mut each_parse_at = vec![];
534    let mut each_scan_at = vec![];
535    let mut each_print_to = vec![];
536    let mut each_to_bnf = vec![];
537    let mut each_first = vec![];
538    for variant in &data.variants {
539        let variant_ident = &variant.ident;
540        let tag = Tag::new(quote! { #ident }, Some(quote! {#variant_ident}));
541        let FieldsInfo {
542            constructor,
543            parse_at,
544            scan_at,
545            print_to,
546            to_bnf,
547            first,
548        } = FieldsInfo::from(filtered, tag, &variant.fields);
549        each_constructor.push(constructor);
550        each_parse_at.push(parse_at);
551        each_scan_at.push(scan_at);
552        each_print_to.push(print_to);
553        each_to_bnf.push(to_bnf);
554        each_first.push(first);
555    }
556    let to_bnf = quote! { ::tygr::bnf::Expr::alternation(vec![ #(#each_to_bnf),* ]) };
557    let bnf_ref = bnf_ref(&name, hidden, to_bnf.clone(), transparent);
558    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
559    let n = each_first.len();
560    let self_ty = quote! { #ident #ty_generics };
561    let parse_fn_ty = quote! {
562        fn(&str, usize, ::tygr::State<'_>) -> Option<(#self_ty, usize)>
563    };
564    let scan_fn_ty = quote! {
565        fn(&str, usize, ::tygr::State<'_>) -> Option<usize>
566    };
567    let case_fns: Vec<Ident> = (0..n)
568        .map(|k| Ident::new(&format!("parse_case_{k}"), Span::call_site()))
569        .collect();
570    let scan_fns: Vec<Ident> = (0..n)
571        .map(|k| Ident::new(&format!("scan_case_{k}"), Span::call_site()))
572        .collect();
573    let case_defs = case_fns.iter().zip(&each_parse_at).enumerate().map(|(k, (name, arm))| {
574        let next = if k + 1 < n {
575            let nx = &case_fns[k + 1];
576            quote! { Self::#nx(input, pos, state) }
577        } else {
578            quote! { None }
579        };
580        quote! {
581            #[inline]
582            fn #name(input: &str, pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> Option<(Self, usize)> {
583                if let Some(result) = (|| { #arm })() { return Some(result); }
584                #next
585            }
586        }
587    });
588    let scan_case_defs = scan_fns
589        .iter()
590        .zip(&each_scan_at)
591        .enumerate()
592        .map(|(k, (name, arm))| {
593            let next = if k + 1 < n {
594                let nx = &scan_fns[k + 1];
595                quote! { Self::#nx(input, pos, state) }
596            } else {
597                quote! { None }
598            };
599            quote! {
600                #[inline]
601                fn #name(input: &str, pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> Option<usize> {
602                    if let Some(result) = (|| { #arm })() { return Some(result); }
603                    #next
604                }
605            }
606        });
607    // slots 0-255: u8 / byte / ascii char
608    // slots 256  : EOF
609    let build_table = |fn_ty: &TokenStream2, fns: &[Ident]| {
610        quote! {{
611            let parsers: [#fn_ty; #n] = [ #(<#self_ty>::#fns),* ];
612            let mut table: [#fn_ty; 257] = [parsers[#n - 1]; 257];
613            let mut first = 0usize;
614            while first < 257 {
615                let mut case = 0usize;
616                while case < #n {
617                    if CONTAINS_NIL[case] || (first <= 255 && CONTAINS_BYTE[case][first]) {
618                        table[first] = parsers[case];
619                        break;
620                    }
621                    case += 1;
622                }
623                first += 1;
624            }
625            table
626        }}
627    };
628    let parse_table = build_table(&parse_fn_ty, &case_fns);
629    let scan_table = build_table(&scan_fn_ty, &scan_fns);
630    const DISPATCH_THRESHOLD: usize = 4;
631    let (dispatch_body, scan_dispatch_body, case_impls) = if n >= DISPATCH_THRESHOLD {
632        (
633            quote! {
634                const CONTAINS_NIL: [bool; #n] = [ #(<#each_first as ::tygr::First>::CONTAINS_NIL),* ];
635                const CONTAINS_BYTE: [[bool; 256]; #n] = [ #(<#each_first as ::tygr::First>::CONTAINS_BYTE),* ];
636                const DISPATCH: [#parse_fn_ty; 257] = #parse_table;
637                let first = input.as_bytes().get(pos).map(|&first| first as usize).unwrap_or(256);
638                DISPATCH[first](input, pos, state)
639            },
640            quote! {
641                const CONTAINS_NIL: [bool; #n] = [ #(<#each_first as ::tygr::First>::CONTAINS_NIL),* ];
642                const CONTAINS_BYTE: [[bool; 256]; #n] = [ #(<#each_first as ::tygr::First>::CONTAINS_BYTE),* ];
643                const DISPATCH: [#scan_fn_ty; 257] = #scan_table;
644                let first = input.as_bytes().get(pos).map(|&first| first as usize).unwrap_or(256);
645                DISPATCH[first](input, pos, state)
646            },
647            quote! {
648                #(#case_defs)*
649                #(#scan_case_defs)*
650            },
651        )
652    } else {
653        (
654            quote! {
655                #( if let Some(result) = (|| { #each_parse_at })() { return Some(result); } )*
656                None
657            },
658            quote! {
659                #( if let Some(result) = (|| { #each_scan_at })() { return Some(result); } )*
660                None
661            },
662            quote! {},
663        )
664    };
665    let parse_body = with_node(transparent, dispatch_body);
666    let scan_body = with_node(transparent, scan_dispatch_body);
667    let first = {
668        let mut the_first = quote! { ::tygr::EmptyByteSet };
669        for first in each_first {
670            the_first = quote! { <#the_first as ::tygr::First>::Union<#first> };
671        }
672        the_first
673    };
674    Ok(quote! {
675        impl #impl_generics #ident #ty_generics #where_clause {
676            #case_impls
677
678            #[inline]
679            fn parse_case_none(_input: &str, _pos: usize, _state: ::tygr::State) -> Option<(Self, usize)> {
680                None
681            }
682        }
683
684        impl #impl_generics ::tygr::Grammar for #ident #ty_generics #where_clause {
685            type First = #first;
686
687            #[inline]
688            fn parse_at(input: &str, pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> Option<(Self, usize)> {
689                #parse_body
690            }
691
692            #[inline]
693            fn scan_at(input: &str, pos: usize, #[allow(unused_variables, unused_mut)] mut state: ::tygr::State) -> Option<usize> {
694                #scan_body
695            }
696
697            fn print_to(&self, buf: &mut ::std::string::String) {
698                match self {
699                    #(#each_constructor => { #each_print_to }),*
700                }
701            }
702
703            fn to_bnf() -> ::tygr::bnf::Expr {
704                #bnf_ref
705            }
706        }
707
708        impl #impl_generics ::tygr::GrammarRule for #ident #ty_generics #where_clause {
709            const NAME: &'static str = #name;
710
711            fn to_bnf_def() -> ::tygr::bnf::Expr {
712                #to_bnf
713            }
714        }
715    })
716}