pest_generator/
generator.rs

1// pest. The Elegant Parser
2// Copyright (c) 2018 DragoÈ™ Tiselice
3//
4// Licensed under the Apache License, Version 2.0
5// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. All files in the project carrying such notice may not be copied,
8// modified, or distributed except according to those terms.
9
10//! Helpers to generate the code for the Parser `derive``.
11
12use std::path::PathBuf;
13
14use proc_macro2::TokenStream;
15use quote::{ToTokens, TokenStreamExt};
16use syn::{self, Ident};
17
18use pest::unicode::unicode_property_names;
19use pest_meta::ast::*;
20use pest_meta::optimizer::*;
21
22use crate::docs::DocComment;
23use crate::parse_derive::ParsedDerive;
24
25/// Generates the corresponding parser based based on the processed macro input. If `include_grammar`
26/// is set to true, it'll generate an explicit "include_str" statement (done in pest_derive, but
27/// turned off in the local bootstrap).
28pub fn generate(
29    parsed_derive: ParsedDerive,
30    paths: Vec<PathBuf>,
31    rules: Vec<OptimizedRule>,
32    defaults: Vec<&str>,
33    doc_comment: &DocComment,
34    include_grammar: bool,
35) -> TokenStream {
36    let uses_eoi = defaults.iter().any(|name| *name == "EOI");
37    let name = parsed_derive.name;
38    let builtins = generate_builtin_rules();
39    let include_fix = if include_grammar {
40        generate_include(&name, paths)
41    } else {
42        quote!()
43    };
44    let rule_enum = generate_enum(&rules, doc_comment, uses_eoi, parsed_derive.non_exhaustive);
45    let patterns = generate_patterns(&rules, uses_eoi);
46    let skip = generate_skip(&rules);
47
48    let mut rules: Vec<_> = rules.into_iter().map(generate_rule).collect();
49    rules.extend(builtins.into_iter().filter_map(|(builtin, tokens)| {
50        if defaults.contains(&builtin) {
51            Some(tokens)
52        } else {
53            None
54        }
55    }));
56
57    let (impl_generics, ty_generics, where_clause) = parsed_derive.generics.split_for_impl();
58
59    let result = result_type();
60
61    let parser_impl = quote! {
62        #[allow(clippy::all)]
63        impl #impl_generics ::pest::Parser<Rule> for #name #ty_generics #where_clause {
64            fn parse<'i>(
65                rule: Rule,
66                input: &'i str
67            ) -> #result<
68                ::pest::iterators::Pairs<'i, Rule>,
69                ::pest::error::Error<Rule>
70            > {
71                mod rules {
72                    #![allow(clippy::upper_case_acronyms)]
73                    pub mod hidden {
74                        use super::super::Rule;
75                        #skip
76                    }
77
78                    pub mod visible {
79                        use super::super::Rule;
80                        #( #rules )*
81                    }
82
83                    pub use self::visible::*;
84                }
85
86                ::pest::state(input, |state| {
87                    match rule {
88                        #patterns
89                    }
90                })
91            }
92        }
93    };
94
95    quote! {
96        #include_fix
97        #rule_enum
98        #parser_impl
99    }
100}
101
102// Note: All builtin rules should be validated as pest builtins in meta/src/validator.rs.
103// Some should also be keywords.
104fn generate_builtin_rules() -> Vec<(&'static str, TokenStream)> {
105    let mut builtins = Vec::new();
106
107    insert_builtin!(builtins, ANY, state.skip(1));
108    insert_builtin!(
109        builtins,
110        EOI,
111        state.rule(Rule::EOI, |state| state.end_of_input())
112    );
113    insert_builtin!(builtins, SOI, state.start_of_input());
114    insert_builtin!(builtins, PEEK, state.stack_peek());
115    insert_builtin!(builtins, PEEK_ALL, state.stack_match_peek());
116    insert_builtin!(builtins, POP, state.stack_pop());
117    insert_builtin!(builtins, POP_ALL, state.stack_match_pop());
118    insert_builtin!(builtins, DROP, state.stack_drop());
119
120    insert_builtin!(builtins, ASCII_DIGIT, state.match_range('0'..'9'));
121    insert_builtin!(builtins, ASCII_NONZERO_DIGIT, state.match_range('1'..'9'));
122    insert_builtin!(builtins, ASCII_BIN_DIGIT, state.match_range('0'..'1'));
123    insert_builtin!(builtins, ASCII_OCT_DIGIT, state.match_range('0'..'7'));
124    insert_builtin!(
125        builtins,
126        ASCII_HEX_DIGIT,
127        state
128            .match_range('0'..'9')
129            .or_else(|state| state.match_range('a'..'f'))
130            .or_else(|state| state.match_range('A'..'F'))
131    );
132    insert_builtin!(builtins, ASCII_ALPHA_LOWER, state.match_range('a'..'z'));
133    insert_builtin!(builtins, ASCII_ALPHA_UPPER, state.match_range('A'..'Z'));
134    insert_builtin!(
135        builtins,
136        ASCII_ALPHA,
137        state
138            .match_range('a'..'z')
139            .or_else(|state| state.match_range('A'..'Z'))
140    );
141    insert_builtin!(
142        builtins,
143        ASCII_ALPHANUMERIC,
144        state
145            .match_range('a'..'z')
146            .or_else(|state| state.match_range('A'..'Z'))
147            .or_else(|state| state.match_range('0'..'9'))
148    );
149    insert_builtin!(builtins, ASCII, state.match_range('\x00'..'\x7f'));
150    insert_builtin!(
151        builtins,
152        NEWLINE,
153        state
154            .match_string("\n")
155            .or_else(|state| state.match_string("\r\n"))
156            .or_else(|state| state.match_string("\r"))
157    );
158
159    let box_ty = box_type();
160
161    for property in unicode_property_names() {
162        let property_ident: Ident = syn::parse_str(property).unwrap();
163        // insert manually for #property substitution
164        builtins.push((property, quote! {
165            #[inline]
166            #[allow(dead_code, non_snake_case, unused_variables)]
167            fn #property_ident(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
168                state.match_char_by(::pest::unicode::#property_ident)
169            }
170        }));
171    }
172    builtins
173}
174
175/// Generate Rust `include_str!` for grammar files, then Cargo will watch changes in grammars.
176fn generate_include(name: &Ident, paths: Vec<PathBuf>) -> TokenStream {
177    let const_name = format_ident!("_PEST_GRAMMAR_{}", name);
178    // Need to make this relative to the current directory since the path to the file
179    // is derived from the CARGO_MANIFEST_DIR environment variable
180    let current_dir = std::env::current_dir().expect("Unable to get current directory");
181
182    let include_tokens = paths.iter().map(|path| {
183        let path = path.to_str().expect("non-Unicode path");
184
185        let relative_path = current_dir
186            .join(path)
187            .to_str()
188            .expect("path contains invalid unicode")
189            .to_string();
190
191        quote! {
192            include_str!(#relative_path)
193        }
194    });
195
196    let len = include_tokens.len();
197    quote! {
198        #[allow(non_upper_case_globals)]
199        const #const_name: [&'static str; #len] = [
200            #(#include_tokens),*
201        ];
202    }
203}
204
205fn generate_enum(
206    rules: &[OptimizedRule],
207    doc_comment: &DocComment,
208    uses_eoi: bool,
209    non_exhaustive: bool,
210) -> TokenStream {
211    let rule_variants = rules.iter().map(|rule| {
212        let rule_name = format_ident!("r#{}", rule.name);
213
214        match doc_comment.line_docs.get(&rule.name) {
215            Some(doc) => quote! {
216                #[doc = #doc]
217                #rule_name
218            },
219            None => quote! {
220                #rule_name
221            },
222        }
223    });
224
225    let grammar_doc = &doc_comment.grammar_doc;
226    let mut result = if grammar_doc.is_empty() {
227        quote! {
228            #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
229            #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
230        }
231    } else {
232        quote! {
233            #[doc = #grammar_doc]
234            #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
235            #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
236        }
237    };
238    if non_exhaustive {
239        result.append_all(quote! {
240            #[non_exhaustive]
241        });
242    }
243    result.append_all(quote! {
244        pub enum Rule
245    });
246    if uses_eoi {
247        result.append_all(quote! {
248            {
249                #[doc = "End-of-input"]
250                EOI,
251                #( #rule_variants ),*
252            }
253        });
254    } else {
255        result.append_all(quote! {
256            {
257                #( #rule_variants ),*
258            }
259        })
260    };
261
262    let rules = rules.iter().map(|rule| {
263        let rule_name = format_ident!("r#{}", rule.name);
264        quote! { #rule_name }
265    });
266
267    result.append_all(quote! {
268        impl Rule {
269            pub fn all_rules() -> &'static[Rule] {
270                &[ #(Rule::#rules), * ]
271            }
272        }
273    });
274
275    result
276}
277
278fn generate_patterns(rules: &[OptimizedRule], uses_eoi: bool) -> TokenStream {
279    let mut rules: Vec<TokenStream> = rules
280        .iter()
281        .map(|rule| {
282            let rule = format_ident!("r#{}", rule.name);
283
284            quote! {
285                Rule::#rule => rules::#rule(state)
286            }
287        })
288        .collect();
289
290    if uses_eoi {
291        rules.push(quote! {
292            Rule::EOI => rules::EOI(state)
293        });
294    }
295
296    quote! {
297        #( #rules ),*
298    }
299}
300
301fn generate_rule(rule: OptimizedRule) -> TokenStream {
302    let name = format_ident!("r#{}", rule.name);
303    let expr = if rule.ty == RuleType::Atomic || rule.ty == RuleType::CompoundAtomic {
304        generate_expr_atomic(rule.expr)
305    } else if rule.name == "WHITESPACE" || rule.name == "COMMENT" {
306        let atomic = generate_expr_atomic(rule.expr);
307
308        quote! {
309            state.atomic(::pest::Atomicity::Atomic, |state| {
310                #atomic
311            })
312        }
313    } else {
314        generate_expr(rule.expr)
315    };
316
317    let box_ty = box_type();
318
319    match rule.ty {
320        RuleType::Normal => quote! {
321            #[inline]
322            #[allow(non_snake_case, unused_variables)]
323            pub fn #name(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
324                state.rule(Rule::#name, |state| {
325                    #expr
326                })
327            }
328        },
329        RuleType::Silent => quote! {
330            #[inline]
331            #[allow(non_snake_case, unused_variables)]
332            pub fn #name(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
333                #expr
334            }
335        },
336        RuleType::Atomic => quote! {
337            #[inline]
338            #[allow(non_snake_case, unused_variables)]
339            pub fn #name(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
340                state.rule(Rule::#name, |state| {
341                    state.atomic(::pest::Atomicity::Atomic, |state| {
342                        #expr
343                    })
344                })
345            }
346        },
347        RuleType::CompoundAtomic => quote! {
348            #[inline]
349            #[allow(non_snake_case, unused_variables)]
350            pub fn #name(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
351                state.atomic(::pest::Atomicity::CompoundAtomic, |state| {
352                    state.rule(Rule::#name, |state| {
353                        #expr
354                    })
355                })
356            }
357        },
358        RuleType::NonAtomic => quote! {
359            #[inline]
360            #[allow(non_snake_case, unused_variables)]
361            pub fn #name(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
362                state.atomic(::pest::Atomicity::NonAtomic, |state| {
363                    state.rule(Rule::#name, |state| {
364                        #expr
365                    })
366                })
367            }
368        },
369    }
370}
371
372fn generate_skip(rules: &[OptimizedRule]) -> TokenStream {
373    let whitespace = rules.iter().any(|rule| rule.name == "WHITESPACE");
374    let comment = rules.iter().any(|rule| rule.name == "COMMENT");
375
376    match (whitespace, comment) {
377        (false, false) => generate_rule!(skip, Ok(state)),
378        (true, false) => generate_rule!(
379            skip,
380            if state.atomicity() == ::pest::Atomicity::NonAtomic {
381                state.repeat(|state| super::visible::WHITESPACE(state))
382            } else {
383                Ok(state)
384            }
385        ),
386        (false, true) => generate_rule!(
387            skip,
388            if state.atomicity() == ::pest::Atomicity::NonAtomic {
389                state.repeat(|state| super::visible::COMMENT(state))
390            } else {
391                Ok(state)
392            }
393        ),
394        (true, true) => generate_rule!(
395            skip,
396            if state.atomicity() == ::pest::Atomicity::NonAtomic {
397                state.sequence(|state| {
398                    state
399                        .repeat(|state| super::visible::WHITESPACE(state))
400                        .and_then(|state| {
401                            state.repeat(|state| {
402                                state.sequence(|state| {
403                                    super::visible::COMMENT(state).and_then(|state| {
404                                        state.repeat(|state| super::visible::WHITESPACE(state))
405                                    })
406                                })
407                            })
408                        })
409                })
410            } else {
411                Ok(state)
412            }
413        ),
414    }
415}
416
417fn generate_expr(expr: OptimizedExpr) -> TokenStream {
418    match expr {
419        OptimizedExpr::Str(string) => {
420            quote! {
421                state.match_string(#string)
422            }
423        }
424        OptimizedExpr::Insens(string) => {
425            quote! {
426                state.match_insensitive(#string)
427            }
428        }
429        OptimizedExpr::Range(start, end) => {
430            let start = start.chars().next().unwrap();
431            let end = end.chars().next().unwrap();
432
433            quote! {
434                state.match_range(#start..#end)
435            }
436        }
437        OptimizedExpr::Ident(ident) => {
438            let ident = format_ident!("r#{}", ident);
439            quote! { self::#ident(state) }
440        }
441        OptimizedExpr::PeekSlice(start, end_) => {
442            let end = QuoteOption(end_);
443            quote! {
444                state.stack_match_peek_slice(#start, #end, ::pest::MatchDir::BottomToTop)
445            }
446        }
447        OptimizedExpr::PosPred(expr) => {
448            let expr = generate_expr(*expr);
449
450            quote! {
451                state.lookahead(true, |state| {
452                    #expr
453                })
454            }
455        }
456        OptimizedExpr::NegPred(expr) => {
457            let expr = generate_expr(*expr);
458
459            quote! {
460                state.lookahead(false, |state| {
461                    #expr
462                })
463            }
464        }
465        OptimizedExpr::Seq(lhs, rhs) => {
466            let head = generate_expr(*lhs);
467            let mut tail = vec![];
468            let mut current = *rhs;
469
470            while let OptimizedExpr::Seq(lhs, rhs) = current {
471                tail.push(generate_expr(*lhs));
472                current = *rhs;
473            }
474            tail.push(generate_expr(current));
475
476            quote! {
477                state.sequence(|state| {
478                    #head
479                    #(
480                        .and_then(|state| {
481                            super::hidden::skip(state)
482                        }).and_then(|state| {
483                            #tail
484                        })
485                    )*
486                })
487            }
488        }
489        OptimizedExpr::Choice(lhs, rhs) => {
490            let head = generate_expr(*lhs);
491            let mut tail = vec![];
492            let mut current = *rhs;
493
494            while let OptimizedExpr::Choice(lhs, rhs) = current {
495                tail.push(generate_expr(*lhs));
496                current = *rhs;
497            }
498            tail.push(generate_expr(current));
499
500            quote! {
501                #head
502                #(
503                    .or_else(|state| {
504                        #tail
505                    })
506                )*
507            }
508        }
509        OptimizedExpr::Opt(expr) => {
510            let expr = generate_expr(*expr);
511
512            quote! {
513                state.optional(|state| {
514                    #expr
515                })
516            }
517        }
518        OptimizedExpr::Rep(expr) => {
519            let expr = generate_expr(*expr);
520
521            quote! {
522                state.sequence(|state| {
523                    state.optional(|state| {
524                        #expr.and_then(|state| {
525                            state.repeat(|state| {
526                                state.sequence(|state| {
527                                    super::hidden::skip(
528                                        state
529                                    ).and_then(|state| {
530                                        #expr
531                                    })
532                                })
533                            })
534                        })
535                    })
536                })
537            }
538        }
539        #[cfg(feature = "grammar-extras")]
540        OptimizedExpr::RepOnce(expr) => {
541            let expr = generate_expr(*expr);
542
543            quote! {
544                state.sequence(|state| {
545                    #expr.and_then(|state| {
546                        state.repeat(|state| {
547                            state.sequence(|state| {
548                                super::hidden::skip(
549                                    state
550                                ).and_then(|state| {
551                                    #expr
552                                })
553                            })
554                        })
555                    })
556                })
557            }
558        }
559        OptimizedExpr::Skip(strings) => {
560            quote! {
561                let strings = [#(#strings),*];
562
563                state.skip_until(&strings)
564            }
565        }
566        OptimizedExpr::Push(expr) => {
567            let expr = generate_expr(*expr);
568
569            quote! {
570                state.stack_push(|state| #expr)
571            }
572        }
573        #[cfg(feature = "grammar-extras")]
574        OptimizedExpr::PushLiteral(string) => {
575            quote! {
576                state.stack_push_literal(#string)
577            }
578        }
579        OptimizedExpr::RestoreOnErr(expr) => {
580            let expr = generate_expr(*expr);
581
582            quote! {
583                state.restore_on_err(|state| #expr)
584            }
585        }
586        #[cfg(feature = "grammar-extras")]
587        OptimizedExpr::NodeTag(expr, tag) => match *expr {
588            OptimizedExpr::Opt(expr) => {
589                let expr = generate_expr(*expr);
590                quote! {
591                    state.optional(|state| {
592                        #expr.and_then(|state| state.tag_node(#tag))
593                    })
594                }
595            }
596            OptimizedExpr::Rep(expr) => {
597                let expr = generate_expr(*expr);
598                quote! {
599                    state.sequence(|state| {
600                        state.optional(|state| {
601                            #expr.and_then(|state| {
602                                state.repeat(|state| {
603                                    state.sequence(|state| {
604                                        super::hidden::skip(
605                                            state
606                                        ).and_then(|state| {
607                                            #expr.and_then(|state| state.tag_node(#tag))
608                                        })
609                                    })
610                                })
611                            }).and_then(|state| state.tag_node(#tag))
612                        })
613                    })
614                }
615            }
616            expr => {
617                let expr = generate_expr(expr);
618                quote! {
619                    #expr.and_then(|state| state.tag_node(#tag))
620                }
621            }
622        },
623    }
624}
625
626fn generate_expr_atomic(expr: OptimizedExpr) -> TokenStream {
627    match expr {
628        OptimizedExpr::Str(string) => {
629            quote! {
630                state.match_string(#string)
631            }
632        }
633        OptimizedExpr::Insens(string) => {
634            quote! {
635                state.match_insensitive(#string)
636            }
637        }
638        OptimizedExpr::Range(start, end) => {
639            let start = start.chars().next().unwrap();
640            let end = end.chars().next().unwrap();
641
642            quote! {
643                state.match_range(#start..#end)
644            }
645        }
646        OptimizedExpr::Ident(ident) => {
647            let ident = format_ident!("r#{}", ident);
648            quote! { self::#ident(state) }
649        }
650        OptimizedExpr::PeekSlice(start, end_) => {
651            let end = QuoteOption(end_);
652            quote! {
653                state.stack_match_peek_slice(#start, #end, ::pest::MatchDir::BottomToTop)
654            }
655        }
656        OptimizedExpr::PosPred(expr) => {
657            let expr = generate_expr_atomic(*expr);
658
659            quote! {
660                state.lookahead(true, |state| {
661                    #expr
662                })
663            }
664        }
665        OptimizedExpr::NegPred(expr) => {
666            let expr = generate_expr_atomic(*expr);
667
668            quote! {
669                state.lookahead(false, |state| {
670                    #expr
671                })
672            }
673        }
674        OptimizedExpr::Seq(lhs, rhs) => {
675            let head = generate_expr_atomic(*lhs);
676            let mut tail = vec![];
677            let mut current = *rhs;
678
679            while let OptimizedExpr::Seq(lhs, rhs) = current {
680                tail.push(generate_expr_atomic(*lhs));
681                current = *rhs;
682            }
683            tail.push(generate_expr_atomic(current));
684
685            quote! {
686                state.sequence(|state| {
687                    #head
688                    #(
689                        .and_then(|state| {
690                            #tail
691                        })
692                    )*
693                })
694            }
695        }
696        OptimizedExpr::Choice(lhs, rhs) => {
697            let head = generate_expr_atomic(*lhs);
698            let mut tail = vec![];
699            let mut current = *rhs;
700
701            while let OptimizedExpr::Choice(lhs, rhs) = current {
702                tail.push(generate_expr_atomic(*lhs));
703                current = *rhs;
704            }
705            tail.push(generate_expr_atomic(current));
706
707            quote! {
708                #head
709                #(
710                    .or_else(|state| {
711                        #tail
712                    })
713                )*
714            }
715        }
716        OptimizedExpr::Opt(expr) => {
717            let expr = generate_expr_atomic(*expr);
718
719            quote! {
720                state.optional(|state| {
721                    #expr
722                })
723            }
724        }
725        OptimizedExpr::Rep(expr) => {
726            let expr = generate_expr_atomic(*expr);
727
728            quote! {
729                state.repeat(|state| {
730                    #expr
731                })
732            }
733        }
734        #[cfg(feature = "grammar-extras")]
735        OptimizedExpr::RepOnce(expr) => {
736            let expr = generate_expr_atomic(*expr);
737
738            quote! {
739                state.sequence(|state| {
740                    #expr.and_then(|state| {
741                        state.repeat(|state| {
742                            state.sequence(|state| {
743                                #expr
744                            })
745                        })
746                    })
747                })
748            }
749        }
750        OptimizedExpr::Skip(strings) => {
751            quote! {
752                let strings = [#(#strings),*];
753
754                state.skip_until(&strings)
755            }
756        }
757        OptimizedExpr::Push(expr) => {
758            let expr = generate_expr_atomic(*expr);
759
760            quote! {
761                state.stack_push(|state| #expr)
762            }
763        }
764        #[cfg(feature = "grammar-extras")]
765        OptimizedExpr::PushLiteral(string) => {
766            quote! {
767                state.stack_push_literal(#string)
768            }
769        }
770        OptimizedExpr::RestoreOnErr(expr) => {
771            let expr = generate_expr_atomic(*expr);
772
773            quote! {
774                state.restore_on_err(|state| #expr)
775            }
776        }
777        #[cfg(feature = "grammar-extras")]
778        OptimizedExpr::NodeTag(expr, tag) => match *expr {
779            OptimizedExpr::Opt(expr) => {
780                let expr = generate_expr_atomic(*expr);
781
782                quote! {
783                    state.optional(|state| {
784                        #expr.and_then(|state| state.tag_node(#tag))
785                    })
786                }
787            }
788            OptimizedExpr::Rep(expr) => {
789                let expr = generate_expr_atomic(*expr);
790
791                quote! {
792                    state.repeat(|state| {
793                        #expr.and_then(|state| state.tag_node(#tag))
794                    })
795                }
796            }
797            expr => {
798                let expr = generate_expr_atomic(expr);
799                quote! {
800                    #expr.and_then(|state| state.tag_node(#tag))
801                }
802            }
803        },
804    }
805}
806
807struct QuoteOption<T>(Option<T>);
808
809impl<T: ToTokens> ToTokens for QuoteOption<T> {
810    fn to_tokens(&self, tokens: &mut TokenStream) {
811        let option = option_type();
812        tokens.append_all(match self.0 {
813            Some(ref t) => quote! { #option::Some(#t) },
814            None => quote! { #option::None },
815        });
816    }
817}
818
819fn box_type() -> TokenStream {
820    #[cfg(feature = "std")]
821    quote! { ::std::boxed::Box }
822
823    #[cfg(not(feature = "std"))]
824    quote! { ::alloc::boxed::Box }
825}
826
827fn result_type() -> TokenStream {
828    #[cfg(feature = "std")]
829    quote! { ::std::result::Result }
830
831    #[cfg(not(feature = "std"))]
832    quote! { ::core::result::Result }
833}
834
835fn option_type() -> TokenStream {
836    #[cfg(feature = "std")]
837    quote! { ::std::option::Option }
838
839    #[cfg(not(feature = "std"))]
840    quote! { ::core::option::Option }
841}
842
843#[cfg(test)]
844mod tests {
845    use super::*;
846
847    use proc_macro2::Span;
848    use std::collections::HashMap;
849    use syn::Generics;
850
851    #[test]
852    fn rule_enum_simple() {
853        let rules = vec![OptimizedRule {
854            name: "f".to_owned(),
855            ty: RuleType::Normal,
856            expr: OptimizedExpr::Ident("g".to_owned()),
857        }];
858
859        let mut line_docs = HashMap::new();
860        line_docs.insert("f".to_owned(), "This is rule comment".to_owned());
861
862        let doc_comment = &DocComment {
863            grammar_doc: "Rule doc\nhello".to_owned(),
864            line_docs,
865        };
866
867        assert_eq!(
868            generate_enum(&rules, doc_comment, false, false).to_string(),
869            quote! {
870                #[doc = "Rule doc\nhello"]
871                #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
872                #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
873                pub enum Rule {
874                    #[doc = "This is rule comment"]
875                    r#f
876                }
877                impl Rule {
878                    pub fn all_rules() -> &'static [Rule] {
879                        &[Rule::r#f]
880                    }
881                }
882            }
883            .to_string()
884        );
885    }
886
887    #[test]
888    fn rule_empty_doc() {
889        let rules = vec![OptimizedRule {
890            name: "f".to_owned(),
891            ty: RuleType::Normal,
892            expr: OptimizedExpr::Ident("g".to_owned()),
893        }];
894
895        let mut line_docs = HashMap::new();
896        line_docs.insert("f".to_owned(), "This is rule comment".to_owned());
897
898        let doc_comment = &DocComment {
899            grammar_doc: "".to_owned(),
900            line_docs,
901        };
902
903        assert_eq!(
904            generate_enum(&rules, doc_comment, false, false).to_string(),
905            quote! {
906                #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
907                #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
908                pub enum Rule {
909                    #[doc = "This is rule comment"]
910                    r#f
911                }
912                impl Rule {
913                    pub fn all_rules() -> &'static [Rule] {
914                        &[Rule::r#f]
915                    }
916                }
917            }
918            .to_string()
919        );
920    }
921
922    #[test]
923    fn sequence() {
924        let expr = OptimizedExpr::Seq(
925            Box::new(OptimizedExpr::Str("a".to_owned())),
926            Box::new(OptimizedExpr::Seq(
927                Box::new(OptimizedExpr::Str("b".to_owned())),
928                Box::new(OptimizedExpr::Seq(
929                    Box::new(OptimizedExpr::Str("c".to_owned())),
930                    Box::new(OptimizedExpr::Str("d".to_owned())),
931                )),
932            )),
933        );
934
935        assert_eq!(
936            generate_expr(expr).to_string(),
937            quote! {
938                state.sequence(|state| {
939                    state.match_string("a").and_then(|state| {
940                        super::hidden::skip(state)
941                    }).and_then(|state| {
942                        state.match_string("b")
943                    }).and_then(|state| {
944                        super::hidden::skip(state)
945                    }).and_then(|state| {
946                        state.match_string("c")
947                    }).and_then(|state| {
948                        super::hidden::skip(state)
949                    }).and_then(|state| {
950                        state.match_string("d")
951                    })
952                })
953            }
954            .to_string()
955        );
956    }
957
958    #[test]
959    fn sequence_atomic() {
960        let expr = OptimizedExpr::Seq(
961            Box::new(OptimizedExpr::Str("a".to_owned())),
962            Box::new(OptimizedExpr::Seq(
963                Box::new(OptimizedExpr::Str("b".to_owned())),
964                Box::new(OptimizedExpr::Seq(
965                    Box::new(OptimizedExpr::Str("c".to_owned())),
966                    Box::new(OptimizedExpr::Str("d".to_owned())),
967                )),
968            )),
969        );
970
971        assert_eq!(
972            generate_expr_atomic(expr).to_string(),
973            quote! {
974                state.sequence(|state| {
975                    state.match_string("a").and_then(|state| {
976                        state.match_string("b")
977                    }).and_then(|state| {
978                        state.match_string("c")
979                    }).and_then(|state| {
980                        state.match_string("d")
981                    })
982                })
983            }
984            .to_string()
985        );
986    }
987
988    #[test]
989    fn choice() {
990        let expr = OptimizedExpr::Choice(
991            Box::new(OptimizedExpr::Str("a".to_owned())),
992            Box::new(OptimizedExpr::Choice(
993                Box::new(OptimizedExpr::Str("b".to_owned())),
994                Box::new(OptimizedExpr::Choice(
995                    Box::new(OptimizedExpr::Str("c".to_owned())),
996                    Box::new(OptimizedExpr::Str("d".to_owned())),
997                )),
998            )),
999        );
1000
1001        assert_eq!(
1002            generate_expr(expr).to_string(),
1003            quote! {
1004                state.match_string("a").or_else(|state| {
1005                    state.match_string("b")
1006                }).or_else(|state| {
1007                    state.match_string("c")
1008                }).or_else(|state| {
1009                    state.match_string("d")
1010                })
1011            }
1012            .to_string()
1013        );
1014    }
1015
1016    #[test]
1017    fn choice_atomic() {
1018        let expr = OptimizedExpr::Choice(
1019            Box::new(OptimizedExpr::Str("a".to_owned())),
1020            Box::new(OptimizedExpr::Choice(
1021                Box::new(OptimizedExpr::Str("b".to_owned())),
1022                Box::new(OptimizedExpr::Choice(
1023                    Box::new(OptimizedExpr::Str("c".to_owned())),
1024                    Box::new(OptimizedExpr::Str("d".to_owned())),
1025                )),
1026            )),
1027        );
1028
1029        assert_eq!(
1030            generate_expr_atomic(expr).to_string(),
1031            quote! {
1032                state.match_string("a").or_else(|state| {
1033                    state.match_string("b")
1034                }).or_else(|state| {
1035                    state.match_string("c")
1036                }).or_else(|state| {
1037                    state.match_string("d")
1038                })
1039            }
1040            .to_string()
1041        );
1042    }
1043
1044    #[test]
1045    fn skip() {
1046        let expr = OptimizedExpr::Skip(vec!["a".to_owned(), "b".to_owned()]);
1047
1048        assert_eq!(
1049            generate_expr_atomic(expr).to_string(),
1050            quote! {
1051                let strings = ["a", "b"];
1052
1053                state.skip_until(&strings)
1054            }
1055            .to_string()
1056        );
1057    }
1058
1059    #[test]
1060    #[cfg(feature = "grammar-extras")]
1061    fn push_literal() {
1062        let expr = OptimizedExpr::PushLiteral("a".to_owned());
1063        assert_eq!(
1064            generate_expr_atomic(expr).to_string(),
1065            quote! {
1066                state.stack_push_literal("a")
1067            }
1068            .to_string()
1069        )
1070    }
1071
1072    #[test]
1073    fn expr_complex() {
1074        let expr = OptimizedExpr::Choice(
1075            Box::new(OptimizedExpr::Ident("a".to_owned())),
1076            Box::new(OptimizedExpr::Seq(
1077                Box::new(OptimizedExpr::Range("a".to_owned(), "b".to_owned())),
1078                Box::new(OptimizedExpr::Seq(
1079                    Box::new(OptimizedExpr::NegPred(Box::new(OptimizedExpr::Rep(
1080                        Box::new(OptimizedExpr::Insens("b".to_owned())),
1081                    )))),
1082                    Box::new(OptimizedExpr::PosPred(Box::new(OptimizedExpr::Opt(
1083                        Box::new(OptimizedExpr::Rep(Box::new(OptimizedExpr::Choice(
1084                            Box::new(OptimizedExpr::Str("c".to_owned())),
1085                            Box::new(OptimizedExpr::Str("d".to_owned())),
1086                        )))),
1087                    )))),
1088                )),
1089            )),
1090        );
1091
1092        let sequence = quote! {
1093            state.sequence(|state| {
1094                super::hidden::skip(state).and_then(
1095                    |state| {
1096                        state.match_insensitive("b")
1097                    }
1098                )
1099            })
1100        };
1101        let repeat = quote! {
1102            state.repeat(|state| {
1103                state.sequence(|state| {
1104                    super::hidden::skip(state).and_then(|state| {
1105                        state.match_string("c")
1106                            .or_else(|state| {
1107                                state.match_string("d")
1108                            })
1109                     })
1110                })
1111            })
1112        };
1113        assert_eq!(
1114            generate_expr(expr).to_string(),
1115            quote! {
1116                self::r#a(state).or_else(|state| {
1117                    state.sequence(|state| {
1118                        state.match_range('a'..'b').and_then(|state| {
1119                            super::hidden::skip(state)
1120                        }).and_then(|state| {
1121                            state.lookahead(false, |state| {
1122                                state.sequence(|state| {
1123                                    state.optional(|state| {
1124                                        state.match_insensitive(
1125                                            "b"
1126                                        ).and_then(|state| {
1127                                            state.repeat(|state| {
1128                                                #sequence
1129                                            })
1130                                        })
1131                                    })
1132                                })
1133                            })
1134                        }).and_then(|state| {
1135                            super::hidden::skip(state)
1136                        }).and_then(|state| {
1137                            state.lookahead(true, |state| {
1138                                state.optional(|state| {
1139                                    state.sequence(|state| {
1140                                        state.optional(|state| {
1141                                            state.match_string("c")
1142                                            .or_else(|state| {
1143                                                state.match_string("d")
1144                                            }).and_then(|state| {
1145                                                #repeat
1146                                            })
1147                                        })
1148                                    })
1149                                })
1150                            })
1151                        })
1152                    })
1153                })
1154            }
1155            .to_string()
1156        );
1157    }
1158
1159    #[test]
1160    fn expr_complex_atomic() {
1161        let expr = OptimizedExpr::Choice(
1162            Box::new(OptimizedExpr::Ident("a".to_owned())),
1163            Box::new(OptimizedExpr::Seq(
1164                Box::new(OptimizedExpr::Range("a".to_owned(), "b".to_owned())),
1165                Box::new(OptimizedExpr::Seq(
1166                    Box::new(OptimizedExpr::NegPred(Box::new(OptimizedExpr::Rep(
1167                        Box::new(OptimizedExpr::Insens("b".to_owned())),
1168                    )))),
1169                    Box::new(OptimizedExpr::PosPred(Box::new(OptimizedExpr::Opt(
1170                        Box::new(OptimizedExpr::Rep(Box::new(OptimizedExpr::Choice(
1171                            Box::new(OptimizedExpr::Str("c".to_owned())),
1172                            Box::new(OptimizedExpr::Str("d".to_owned())),
1173                        )))),
1174                    )))),
1175                )),
1176            )),
1177        );
1178
1179        assert_eq!(
1180            generate_expr_atomic(expr).to_string(),
1181            quote! {
1182                self::r#a(state).or_else(|state| {
1183                    state.sequence(|state| {
1184                        state.match_range('a'..'b').and_then(|state| {
1185                            state.lookahead(false, |state| {
1186                                state.repeat(|state| {
1187                                    state.match_insensitive("b")
1188                                })
1189                            })
1190                        }).and_then(|state| {
1191                            state.lookahead(true, |state| {
1192                                state.optional(|state| {
1193                                    state.repeat(|state| {
1194                                        state.match_string("c")
1195                                           .or_else(|state| {
1196                                            state.match_string("d")
1197                                        })
1198                                    })
1199                                })
1200                            })
1201                        })
1202                    })
1203                })
1204            }
1205            .to_string()
1206        );
1207    }
1208
1209    #[test]
1210    fn test_generate_complete() {
1211        let name = Ident::new("MyParser", Span::call_site());
1212        let generics = Generics::default();
1213
1214        let rules = vec![
1215            OptimizedRule {
1216                name: "a".to_owned(),
1217                ty: RuleType::Silent,
1218                expr: OptimizedExpr::Str("b".to_owned()),
1219            },
1220            OptimizedRule {
1221                name: "if".to_owned(),
1222                ty: RuleType::Silent,
1223                expr: OptimizedExpr::Ident("a".to_owned()),
1224            },
1225        ];
1226
1227        let mut line_docs = HashMap::new();
1228        line_docs.insert("if".to_owned(), "If statement".to_owned());
1229
1230        let doc_comment = &DocComment {
1231            line_docs,
1232            grammar_doc: "This is Rule doc\nThis is second line".to_owned(),
1233        };
1234
1235        let defaults = vec!["ANY"];
1236        let result = result_type();
1237        let box_ty = box_type();
1238        let current_dir = std::env::current_dir().expect("Unable to get current directory");
1239
1240        let base_path = current_dir.join("base.pest").to_str().unwrap().to_string();
1241        let test_path = current_dir.join("test.pest").to_str().unwrap().to_string();
1242        let parsed_derive = ParsedDerive {
1243            name,
1244            generics,
1245            non_exhaustive: false,
1246        };
1247        assert_eq!(
1248            generate(parsed_derive, vec![PathBuf::from("base.pest"), PathBuf::from("test.pest")], rules, defaults, doc_comment, true).to_string(),
1249            quote! {
1250                #[allow(non_upper_case_globals)]
1251                const _PEST_GRAMMAR_MyParser: [&'static str; 2usize] = [include_str!(#base_path), include_str!(#test_path)];
1252
1253                #[doc = "This is Rule doc\nThis is second line"]
1254                #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
1255                #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1256                pub enum Rule {
1257                    r#a,
1258                    #[doc = "If statement"]
1259                    r#if
1260                }
1261                impl Rule {
1262                    pub fn all_rules() -> &'static [Rule] {
1263                        &[Rule::r#a, Rule::r#if]
1264                    }
1265                }
1266
1267                #[allow(clippy::all)]
1268                impl ::pest::Parser<Rule> for MyParser {
1269                    fn parse<'i>(
1270                        rule: Rule,
1271                        input: &'i str
1272                    ) -> #result<
1273                        ::pest::iterators::Pairs<'i, Rule>,
1274                        ::pest::error::Error<Rule>
1275                    > {
1276                        mod rules {
1277                            #![allow(clippy::upper_case_acronyms)]
1278                            pub mod hidden {
1279                                use super::super::Rule;
1280
1281                                #[inline]
1282                                #[allow(dead_code, non_snake_case, unused_variables)]
1283                                pub fn skip(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
1284                                    Ok(state)
1285                                }
1286                            }
1287
1288                            pub mod visible {
1289                                use super::super::Rule;
1290
1291                                #[inline]
1292                                #[allow(non_snake_case, unused_variables)]
1293                                pub fn r#a(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
1294                                    state.match_string("b")
1295                                }
1296
1297                                #[inline]
1298                                #[allow(non_snake_case, unused_variables)]
1299                                pub fn r#if(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
1300                                    self::r#a(state)
1301                                }
1302
1303                                #[inline]
1304                                #[allow(dead_code, non_snake_case, unused_variables)]
1305                                pub fn ANY(state: #box_ty<::pest::ParserState<'_, Rule>>) -> ::pest::ParseResult<#box_ty<::pest::ParserState<'_, Rule>>> {
1306                                    state.skip(1)
1307                                }
1308                            }
1309
1310                            pub use self::visible::*;
1311                        }
1312
1313                        ::pest::state(input, |state| {
1314                            match rule {
1315                                Rule::r#a => rules::r#a(state),
1316                                Rule::r#if => rules::r#if(state)
1317                            }
1318                        })
1319                    }
1320                }
1321            }.to_string()
1322        );
1323    }
1324}