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