1use 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
25pub 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
102fn 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 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
175fn generate_include(name: &Ident, paths: Vec<PathBuf>) -> TokenStream {
177 let const_name = format_ident!("_PEST_GRAMMAR_{}", name);
178 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}