1use 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
97fn 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 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
170fn generate_include(name: &Ident, paths: Vec<PathBuf>) -> TokenStream {
172 let const_name = format_ident!("_PEST_GRAMMAR_{}", name);
173 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}