]> git.proxmox.com Git - rustc.git/blame - vendor/pest_meta/src/validator.rs
New upstream version 1.65.0+dfsg1
[rustc.git] / vendor / pest_meta / src / validator.rs
CommitLineData
9fa01778
XL
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
f2b60f7d 10use once_cell::sync::Lazy;
9fa01778
XL
11use std::collections::{HashMap, HashSet};
12
13use pest::error::{Error, ErrorVariant, InputLocation};
14use pest::iterators::Pairs;
15use pest::Span;
16
f2b60f7d
FG
17use crate::parser::{ParserExpr, ParserNode, ParserRule, Rule};
18use crate::UNICODE_PROPERTY_NAMES;
19
20static RUST_KEYWORDS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
21 [
22 "abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do",
23 "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop",
24 "macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pure",
25 "pub", "ref", "return", "Self", "self", "sizeof", "static", "struct", "super", "trait",
26 "true", "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
27 ]
28 .iter()
29 .cloned()
30 .collect()
31});
32
33static PEST_KEYWORDS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
34 [
35 "_", "ANY", "DROP", "EOI", "PEEK", "PEEK_ALL", "POP", "POP_ALL", "PUSH", "SOI",
36 ]
37 .iter()
38 .cloned()
39 .collect()
40});
41
42static BUILTINS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
43 [
44 "ANY",
45 "DROP",
46 "EOI",
47 "PEEK",
48 "PEEK_ALL",
49 "POP",
50 "POP_ALL",
51 "SOI",
52 "ASCII_DIGIT",
53 "ASCII_NONZERO_DIGIT",
54 "ASCII_BIN_DIGIT",
55 "ASCII_OCT_DIGIT",
56 "ASCII_HEX_DIGIT",
57 "ASCII_ALPHA_LOWER",
58 "ASCII_ALPHA_UPPER",
59 "ASCII_ALPHA",
60 "ASCII_ALPHANUMERIC",
61 "ASCII",
62 "NEWLINE",
63 ]
64 .iter()
65 .cloned()
66 .chain(UNICODE_PROPERTY_NAMES.iter().cloned())
67 .collect::<HashSet<&str>>()
68});
69
70pub fn validate_pairs(pairs: Pairs<Rule>) -> Result<Vec<&str>, Vec<Error<Rule>>> {
9fa01778
XL
71 let definitions: Vec<_> = pairs
72 .clone()
73 .filter(|pair| pair.as_rule() == Rule::grammar_rule)
ba9703b0 74 .map(|pair| pair.into_inner().next().unwrap().as_span())
9fa01778
XL
75 .collect();
76 let called_rules: Vec<_> = pairs
77 .clone()
78 .filter(|pair| pair.as_rule() == Rule::grammar_rule)
79 .flat_map(|pair| {
80 pair.into_inner()
81 .flatten()
82 .skip(1)
83 .filter(|pair| pair.as_rule() == Rule::identifier)
ba9703b0 84 .map(|pair| pair.as_span())
9fa01778
XL
85 })
86 .collect();
87
88 let mut errors = vec![];
89
f2b60f7d
FG
90 errors.extend(validate_rust_keywords(&definitions));
91 errors.extend(validate_pest_keywords(&definitions));
9fa01778 92 errors.extend(validate_already_defined(&definitions));
f2b60f7d 93 errors.extend(validate_undefined(&definitions, &called_rules));
9fa01778
XL
94
95 if !errors.is_empty() {
96 return Err(errors);
97 }
98
99 let definitions: HashSet<_> = definitions.iter().map(|span| span.as_str()).collect();
100 let called_rules: HashSet<_> = called_rules.iter().map(|span| span.as_str()).collect();
101
102 let defaults = called_rules.difference(&definitions);
103
104 Ok(defaults.cloned().collect())
105}
106
f2b60f7d
FG
107#[allow(clippy::ptr_arg)]
108pub fn validate_rust_keywords(definitions: &Vec<Span>) -> Vec<Error<Rule>> {
9fa01778
XL
109 let mut errors = vec![];
110
111 for definition in definitions {
112 let name = definition.as_str();
113
f2b60f7d 114 if RUST_KEYWORDS.contains(name) {
9fa01778
XL
115 errors.push(Error::new_from_span(
116 ErrorVariant::CustomError {
117 message: format!("{} is a rust keyword", name),
118 },
f2b60f7d 119 *definition,
9fa01778
XL
120 ))
121 }
122 }
123
124 errors
125}
126
f2b60f7d
FG
127#[allow(clippy::ptr_arg)]
128pub fn validate_pest_keywords(definitions: &Vec<Span>) -> Vec<Error<Rule>> {
9fa01778
XL
129 let mut errors = vec![];
130
131 for definition in definitions {
132 let name = definition.as_str();
133
f2b60f7d 134 if PEST_KEYWORDS.contains(name) {
9fa01778
XL
135 errors.push(Error::new_from_span(
136 ErrorVariant::CustomError {
137 message: format!("{} is a pest keyword", name),
138 },
f2b60f7d 139 *definition,
9fa01778
XL
140 ))
141 }
142 }
143
144 errors
145}
146
147#[allow(clippy::ptr_arg)]
f2b60f7d 148pub fn validate_already_defined(definitions: &Vec<Span>) -> Vec<Error<Rule>> {
9fa01778
XL
149 let mut errors = vec![];
150 let mut defined = HashSet::new();
151
152 for definition in definitions {
153 let name = definition.as_str();
154
155 if defined.contains(&name) {
156 errors.push(Error::new_from_span(
157 ErrorVariant::CustomError {
158 message: format!("rule {} already defined", name),
159 },
f2b60f7d 160 *definition,
9fa01778
XL
161 ))
162 } else {
163 defined.insert(name);
164 }
165 }
166
167 errors
168}
169
f2b60f7d 170#[allow(clippy::ptr_arg)]
9fa01778
XL
171pub fn validate_undefined<'i>(
172 definitions: &Vec<Span<'i>>,
173 called_rules: &Vec<Span<'i>>,
9fa01778
XL
174) -> Vec<Error<Rule>> {
175 let mut errors = vec![];
176 let definitions: HashSet<_> = definitions.iter().map(|span| span.as_str()).collect();
177
178 for rule in called_rules {
179 let name = rule.as_str();
180
f2b60f7d 181 if !definitions.contains(name) && !BUILTINS.contains(name) {
9fa01778
XL
182 errors.push(Error::new_from_span(
183 ErrorVariant::CustomError {
184 message: format!("rule {} is undefined", name),
185 },
f2b60f7d 186 *rule,
9fa01778
XL
187 ))
188 }
189 }
190
191 errors
192}
193
194#[allow(clippy::ptr_arg)]
195pub fn validate_ast<'a, 'i: 'a>(rules: &'a Vec<ParserRule<'i>>) -> Vec<Error<Rule>> {
196 let mut errors = vec![];
197
198 errors.extend(validate_repetition(rules));
199 errors.extend(validate_choices(rules));
200 errors.extend(validate_whitespace_comment(rules));
201 errors.extend(validate_left_recursion(rules));
202
203 errors.sort_by_key(|error| match error.location {
204 InputLocation::Span(span) => span,
205 _ => unreachable!(),
206 });
207
208 errors
209}
210
211fn is_non_progressing<'i>(
212 expr: &ParserExpr<'i>,
213 rules: &HashMap<String, &ParserNode<'i>>,
214 trace: &mut Vec<String>,
215) -> bool {
216 match *expr {
f2b60f7d 217 ParserExpr::Str(ref string) => string.is_empty(),
9fa01778
XL
218 ParserExpr::Ident(ref ident) => {
219 if ident == "soi" || ident == "eoi" {
220 return true;
221 }
222
223 if !trace.contains(ident) {
224 if let Some(node) = rules.get(ident) {
225 trace.push(ident.clone());
226 let result = is_non_progressing(&node.expr, rules, trace);
227 trace.pop().unwrap();
228
229 return result;
230 }
231 }
232
233 false
234 }
235 ParserExpr::PosPred(_) => true,
236 ParserExpr::NegPred(_) => true,
237 ParserExpr::Seq(ref lhs, ref rhs) => {
238 is_non_progressing(&lhs.expr, rules, trace)
239 && is_non_progressing(&rhs.expr, rules, trace)
240 }
241 ParserExpr::Choice(ref lhs, ref rhs) => {
242 is_non_progressing(&lhs.expr, rules, trace)
243 || is_non_progressing(&rhs.expr, rules, trace)
244 }
245 _ => false,
246 }
247}
248
249fn is_non_failing<'i>(
250 expr: &ParserExpr<'i>,
251 rules: &HashMap<String, &ParserNode<'i>>,
252 trace: &mut Vec<String>,
253) -> bool {
254 match *expr {
f2b60f7d 255 ParserExpr::Str(ref string) => string.is_empty(),
9fa01778
XL
256 ParserExpr::Ident(ref ident) => {
257 if !trace.contains(ident) {
258 if let Some(node) = rules.get(ident) {
259 trace.push(ident.clone());
260 let result = is_non_failing(&node.expr, rules, trace);
261 trace.pop().unwrap();
262
263 return result;
264 }
265 }
266
267 false
268 }
269 ParserExpr::Opt(_) => true,
270 ParserExpr::Rep(_) => true,
271 ParserExpr::Seq(ref lhs, ref rhs) => {
272 is_non_failing(&lhs.expr, rules, trace) && is_non_failing(&rhs.expr, rules, trace)
273 }
274 ParserExpr::Choice(ref lhs, ref rhs) => {
275 is_non_failing(&lhs.expr, rules, trace) || is_non_failing(&rhs.expr, rules, trace)
276 }
277 _ => false,
278 }
279}
280
281fn validate_repetition<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> {
282 let mut result = vec![];
283 let map = to_hash_map(rules);
284
285 for rule in rules {
286 let mut errors = rule.node
287 .clone()
288 .filter_map_top_down(|node| match node.expr {
289 ParserExpr::Rep(ref other)
290 | ParserExpr::RepOnce(ref other)
291 | ParserExpr::RepMin(ref other, _) => {
292 if is_non_failing(&other.expr, &map, &mut vec![]) {
293 Some(Error::new_from_span(
294 ErrorVariant::CustomError {
295 message:
296 "expression inside repetition cannot fail and will repeat \
297 infinitely"
298 .to_owned()
299 },
f2b60f7d 300 node.span
9fa01778
XL
301 ))
302 } else if is_non_progressing(&other.expr, &map, &mut vec![]) {
303 Some(Error::new_from_span(
304 ErrorVariant::CustomError {
305 message:
306 "expression inside repetition is non-progressing and will repeat \
307 infinitely"
308 .to_owned(),
309 },
f2b60f7d 310 node.span
9fa01778
XL
311 ))
312 } else {
313 None
314 }
315 }
316 _ => None
317 });
318
319 result.append(&mut errors);
320 }
321
322 result
323}
324
325fn validate_choices<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> {
326 let mut result = vec![];
327 let map = to_hash_map(rules);
328
329 for rule in rules {
330 let mut errors = rule
331 .node
332 .clone()
333 .filter_map_top_down(|node| match node.expr {
334 ParserExpr::Choice(ref lhs, _) => {
335 let node = match lhs.expr {
336 ParserExpr::Choice(_, ref rhs) => rhs,
337 _ => lhs,
338 };
339
340 if is_non_failing(&node.expr, &map, &mut vec![]) {
341 Some(Error::new_from_span(
342 ErrorVariant::CustomError {
343 message:
344 "expression cannot fail; following choices cannot be reached"
345 .to_owned(),
346 },
f2b60f7d 347 node.span,
9fa01778
XL
348 ))
349 } else {
350 None
351 }
352 }
353 _ => None,
354 });
355
356 result.append(&mut errors);
357 }
358
359 result
360}
361
362fn validate_whitespace_comment<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> {
363 let map = to_hash_map(rules);
364
365 rules
366 .iter()
367 .filter_map(|rule| {
ba9703b0 368 if rule.name == "WHITESPACE" || rule.name == "COMMENT" {
9fa01778
XL
369 if is_non_failing(&rule.node.expr, &map, &mut vec![]) {
370 Some(Error::new_from_span(
371 ErrorVariant::CustomError {
372 message: format!(
373 "{} cannot fail and will repeat infinitely",
374 &rule.name
375 ),
376 },
f2b60f7d 377 rule.node.span,
9fa01778
XL
378 ))
379 } else if is_non_progressing(&rule.node.expr, &map, &mut vec![]) {
380 Some(Error::new_from_span(
381 ErrorVariant::CustomError {
382 message: format!(
383 "{} is non-progressing and will repeat infinitely",
384 &rule.name
385 ),
386 },
f2b60f7d 387 rule.node.span,
9fa01778
XL
388 ))
389 } else {
390 None
391 }
392 } else {
393 None
394 }
395 })
396 .collect()
397}
398
399fn validate_left_recursion<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> {
400 left_recursion(to_hash_map(rules))
401}
402
403fn to_hash_map<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> HashMap<String, &'a ParserNode<'i>> {
404 rules.iter().map(|r| (r.name.clone(), &r.node)).collect()
405}
406
9fa01778
XL
407fn left_recursion<'a, 'i: 'a>(rules: HashMap<String, &'a ParserNode<'i>>) -> Vec<Error<Rule>> {
408 fn check_expr<'a, 'i: 'a>(
409 node: &'a ParserNode<'i>,
410 rules: &'a HashMap<String, &ParserNode<'i>>,
411 trace: &mut Vec<String>,
412 ) -> Option<Error<Rule>> {
413 match node.expr.clone() {
414 ParserExpr::Ident(other) => {
415 if trace[0] == other {
416 trace.push(other);
417 let chain = trace
418 .iter()
419 .map(|ident| ident.as_ref())
420 .collect::<Vec<_>>()
421 .join(" -> ");
422
423 return Some(Error::new_from_span(
424 ErrorVariant::CustomError {
425 message: format!(
426 "rule {} is left-recursive ({}); pest::prec_climber might be useful \
427 in this case",
428 node.span.as_str(),
429 chain
430 )
431 },
f2b60f7d 432 node.span
9fa01778
XL
433 ));
434 }
435
436 if !trace.contains(&other) {
437 if let Some(node) = rules.get(&other) {
438 trace.push(other);
439 let result = check_expr(node, rules, trace);
440 trace.pop().unwrap();
441
442 return result;
443 }
444 }
445
446 None
447 }
448 ParserExpr::Seq(ref lhs, ref rhs) => {
449 if is_non_failing(&lhs.expr, rules, &mut vec![trace.last().unwrap().clone()]) {
450 check_expr(rhs, rules, trace)
451 } else {
452 check_expr(lhs, rules, trace)
453 }
454 }
455 ParserExpr::Choice(ref lhs, ref rhs) => {
f2b60f7d 456 check_expr(lhs, rules, trace).or_else(|| check_expr(rhs, rules, trace))
9fa01778 457 }
f2b60f7d
FG
458 ParserExpr::Rep(ref node) => check_expr(node, rules, trace),
459 ParserExpr::RepOnce(ref node) => check_expr(node, rules, trace),
460 ParserExpr::Opt(ref node) => check_expr(node, rules, trace),
461 ParserExpr::PosPred(ref node) => check_expr(node, rules, trace),
462 ParserExpr::NegPred(ref node) => check_expr(node, rules, trace),
463 ParserExpr::Push(ref node) => check_expr(node, rules, trace),
9fa01778
XL
464 _ => None,
465 }
466 }
467
468 let mut errors = vec![];
469
f2b60f7d
FG
470 for (name, node) in &rules {
471 let name = name.clone();
9fa01778
XL
472
473 if let Some(error) = check_expr(node, &rules, &mut vec![name]) {
474 errors.push(error);
475 }
476 }
477
478 errors
479}
480
481#[cfg(test)]
482mod tests {
483 use super::super::parser::{consume_rules, PestParser};
484 use super::super::unwrap_or_report;
485 use super::*;
486 use pest::Parser;
487
488 #[test]
489 #[should_panic(expected = "grammar error
490
491 --> 1:1
492 |
4931 | let = { \"a\" }
494 | ^-^
495 |
496 = let is a rust keyword")]
497 fn rust_keyword() {
498 let input = "let = { \"a\" }";
499 unwrap_or_report(validate_pairs(
500 PestParser::parse(Rule::grammar_rules, input).unwrap(),
501 ));
502 }
503
504 #[test]
505 #[should_panic(expected = "grammar error
506
507 --> 1:1
508 |
5091 | ANY = { \"a\" }
510 | ^-^
511 |
512 = ANY is a pest keyword")]
513 fn pest_keyword() {
514 let input = "ANY = { \"a\" }";
515 unwrap_or_report(validate_pairs(
516 PestParser::parse(Rule::grammar_rules, input).unwrap(),
517 ));
518 }
519
520 #[test]
521 #[should_panic(expected = "grammar error
522
523 --> 1:13
524 |
5251 | a = { \"a\" } a = { \"a\" }
526 | ^
527 |
528 = rule a already defined")]
529 fn already_defined() {
530 let input = "a = { \"a\" } a = { \"a\" }";
531 unwrap_or_report(validate_pairs(
532 PestParser::parse(Rule::grammar_rules, input).unwrap(),
533 ));
534 }
535
536 #[test]
537 #[should_panic(expected = "grammar error
538
539 --> 1:7
540 |
5411 | a = { b }
542 | ^
543 |
544 = rule b is undefined")]
545 fn undefined() {
546 let input = "a = { b }";
547 unwrap_or_report(validate_pairs(
548 PestParser::parse(Rule::grammar_rules, input).unwrap(),
549 ));
550 }
551
552 #[test]
553 fn valid_recursion() {
554 let input = "a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"b\") ~ a }";
555 unwrap_or_report(consume_rules(
556 PestParser::parse(Rule::grammar_rules, input).unwrap(),
557 ));
558 }
559
560 #[test]
561 #[should_panic(expected = "grammar error
562
563 --> 1:16
564 |
ba9703b0 5651 | WHITESPACE = { \"\" }
9fa01778
XL
566 | ^^
567 |
ba9703b0 568 = WHITESPACE cannot fail and will repeat infinitely")]
9fa01778 569 fn non_failing_whitespace() {
ba9703b0 570 let input = "WHITESPACE = { \"\" }";
9fa01778
XL
571 unwrap_or_report(consume_rules(
572 PestParser::parse(Rule::grammar_rules, input).unwrap(),
573 ));
574 }
575
576 #[test]
577 #[should_panic(expected = "grammar error
578
579 --> 1:13
580 |
ba9703b0 5811 | COMMENT = { soi }
9fa01778
XL
582 | ^-^
583 |
ba9703b0 584 = COMMENT is non-progressing and will repeat infinitely")]
9fa01778 585 fn non_progressing_comment() {
ba9703b0 586 let input = "COMMENT = { soi }";
9fa01778
XL
587 unwrap_or_report(consume_rules(
588 PestParser::parse(Rule::grammar_rules, input).unwrap(),
589 ));
590 }
591
592 #[test]
593 #[should_panic(expected = "grammar error
594
595 --> 1:7
596 |
5971 | a = { (\"\")* }
598 | ^---^
599 |
600 = expression inside repetition cannot fail and will repeat infinitely")]
601 fn non_failing_repetition() {
602 let input = "a = { (\"\")* }";
603 unwrap_or_report(consume_rules(
604 PestParser::parse(Rule::grammar_rules, input).unwrap(),
605 ));
606 }
607
608 #[test]
609 #[should_panic(expected = "grammar error
610
611 --> 1:18
612 |
6131 | a = { \"\" } b = { a* }
614 | ^^
615 |
616 = expression inside repetition cannot fail and will repeat infinitely")]
617 fn indirect_non_failing_repetition() {
618 let input = "a = { \"\" } b = { a* }";
619 unwrap_or_report(consume_rules(
620 PestParser::parse(Rule::grammar_rules, input).unwrap(),
621 ));
622 }
623
624 #[test]
625 #[should_panic(expected = "grammar error
626
627 --> 1:20
628 |
6291 | a = { \"a\" ~ (\"b\" ~ (\"\")*) }
630 | ^---^
631 |
632 = expression inside repetition cannot fail and will repeat infinitely")]
633 fn deep_non_failing_repetition() {
634 let input = "a = { \"a\" ~ (\"b\" ~ (\"\")*) }";
635 unwrap_or_report(consume_rules(
636 PestParser::parse(Rule::grammar_rules, input).unwrap(),
637 ));
638 }
639
640 #[test]
641 #[should_panic(expected = "grammar error
642
643 --> 1:7
644 |
6451 | a = { (\"\" ~ &\"a\" ~ !\"a\" ~ (soi | eoi))* }
646 | ^-------------------------------^
647 |
648 = expression inside repetition is non-progressing and will repeat infinitely")]
649 fn non_progressing_repetition() {
650 let input = "a = { (\"\" ~ &\"a\" ~ !\"a\" ~ (soi | eoi))* }";
651 unwrap_or_report(consume_rules(
652 PestParser::parse(Rule::grammar_rules, input).unwrap(),
653 ));
654 }
655
656 #[test]
657 #[should_panic(expected = "grammar error
658
659 --> 1:20
660 |
6611 | a = { !\"a\" } b = { a* }
662 | ^^
663 |
664 = expression inside repetition is non-progressing and will repeat infinitely")]
665 fn indirect_non_progressing_repetition() {
666 let input = "a = { !\"a\" } b = { a* }";
667 unwrap_or_report(consume_rules(
668 PestParser::parse(Rule::grammar_rules, input).unwrap(),
669 ));
670 }
671
672 #[test]
673 #[should_panic(expected = "grammar error
674
675 --> 1:7
676 |
6771 | a = { a }
678 | ^
679 |
680 = rule a is left-recursive (a -> a); pest::prec_climber might be useful in this case")]
681 fn simple_left_recursion() {
682 let input = "a = { a }";
683 unwrap_or_report(consume_rules(
684 PestParser::parse(Rule::grammar_rules, input).unwrap(),
685 ));
686 }
687
688 #[test]
689 #[should_panic(expected = "grammar error
690
691 --> 1:7
692 |
6931 | a = { b } b = { a }
694 | ^
695 |
696 = rule b is left-recursive (b -> a -> b); pest::prec_climber might be useful in this case
697
698 --> 1:17
699 |
7001 | a = { b } b = { a }
701 | ^
702 |
703 = rule a is left-recursive (a -> b -> a); pest::prec_climber might be useful in this case")]
704 fn indirect_left_recursion() {
705 let input = "a = { b } b = { a }";
706 unwrap_or_report(consume_rules(
707 PestParser::parse(Rule::grammar_rules, input).unwrap(),
708 ));
709 }
710
711 #[test]
712 #[should_panic(expected = "grammar error
713
714 --> 1:39
715 |
7161 | a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"\") ~ a }
717 | ^
718 |
719 = rule a is left-recursive (a -> a); pest::prec_climber might be useful in this case")]
720 fn non_failing_left_recursion() {
721 let input = "a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"\") ~ a }";
722 unwrap_or_report(consume_rules(
723 PestParser::parse(Rule::grammar_rules, input).unwrap(),
724 ));
725 }
726
727 #[test]
728 #[should_panic(expected = "grammar error
729
730 --> 1:13
731 |
7321 | a = { \"a\" | a }
733 | ^
734 |
735 = rule a is left-recursive (a -> a); pest::prec_climber might be useful in this case")]
736 fn non_primary_choice_left_recursion() {
737 let input = "a = { \"a\" | a }";
738 unwrap_or_report(consume_rules(
739 PestParser::parse(Rule::grammar_rules, input).unwrap(),
740 ));
741 }
742
743 #[test]
744 #[should_panic(expected = "grammar error
745
746 --> 1:7
747 |
7481 | a = { \"a\"* | \"a\" | \"b\" }
749 | ^--^
750 |
751 = expression cannot fail; following choices cannot be reached")]
752 fn lhs_non_failing_choice() {
753 let input = "a = { \"a\"* | \"a\" | \"b\" }";
754 unwrap_or_report(consume_rules(
755 PestParser::parse(Rule::grammar_rules, input).unwrap(),
756 ));
757 }
758
759 #[test]
760 #[should_panic(expected = "grammar error
761
762 --> 1:13
763 |
7641 | a = { \"a\" | \"a\"* | \"b\" }
765 | ^--^
766 |
767 = expression cannot fail; following choices cannot be reached")]
768 fn lhs_non_failing_choice_middle() {
769 let input = "a = { \"a\" | \"a\"* | \"b\" }";
770 unwrap_or_report(consume_rules(
771 PestParser::parse(Rule::grammar_rules, input).unwrap(),
772 ));
773 }
774
775 #[test]
776 #[should_panic(expected = "grammar error
777
778 --> 1:7
779 |
7801 | a = { b | \"a\" } b = { \"b\"* | \"c\" }
781 | ^
782 |
783 = expression cannot fail; following choices cannot be reached
784
785 --> 1:23
786 |
7871 | a = { b | \"a\" } b = { \"b\"* | \"c\" }
788 | ^--^
789 |
790 = expression cannot fail; following choices cannot be reached")]
791 fn lhs_non_failing_nested_choices() {
792 let input = "a = { b | \"a\" } b = { \"b\"* | \"c\" }";
793 unwrap_or_report(consume_rules(
794 PestParser::parse(Rule::grammar_rules, input).unwrap(),
795 ));
796 }
797
798 #[test]
799 fn skip_can_be_defined() {
800 let input = "skip = { \"\" }";
801 unwrap_or_report(consume_rules(
802 PestParser::parse(Rule::grammar_rules, input).unwrap(),
803 ));
804 }
805}