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