]>
Commit | Line | Data |
---|---|---|
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 | 10 | use once_cell::sync::Lazy; |
9fa01778 XL |
11 | use std::collections::{HashMap, HashSet}; |
12 | ||
13 | use pest::error::{Error, ErrorVariant, InputLocation}; | |
14 | use pest::iterators::Pairs; | |
15 | use pest::Span; | |
16 | ||
f2b60f7d FG |
17 | use crate::parser::{ParserExpr, ParserNode, ParserRule, Rule}; |
18 | use crate::UNICODE_PROPERTY_NAMES; | |
19 | ||
20 | static 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 | ||
33 | static 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 | ||
42 | static 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 | ||
70 | pub 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)] |
108 | pub 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)] |
128 | pub 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 | 148 | pub 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 |
171 | pub 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)] | |
195 | pub 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 | ||
211 | fn 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 | ||
249 | fn 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 | ||
281 | fn 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 | ||
325 | fn 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 | ||
362 | fn 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 | ||
399 | fn validate_left_recursion<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> { | |
400 | left_recursion(to_hash_map(rules)) | |
401 | } | |
402 | ||
403 | fn 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 |
407 | fn 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)] | |
482 | mod 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 | | | |
493 | 1 | 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 | | | |
509 | 1 | 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 | | | |
525 | 1 | 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 | | | |
541 | 1 | 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 | 565 | 1 | 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 | 581 | 1 | 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 | | | |
597 | 1 | 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 | | | |
613 | 1 | 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 | | | |
629 | 1 | 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 | | | |
645 | 1 | 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 | | | |
661 | 1 | 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 | | | |
677 | 1 | 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 | | | |
693 | 1 | 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 | | | |
700 | 1 | 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 | | | |
716 | 1 | 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 | | | |
732 | 1 | 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 | | | |
748 | 1 | 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 | | | |
764 | 1 | 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 | | | |
780 | 1 | a = { b | \"a\" } b = { \"b\"* | \"c\" } | |
781 | | ^ | |
782 | | | |
783 | = expression cannot fail; following choices cannot be reached | |
784 | ||
785 | --> 1:23 | |
786 | | | |
787 | 1 | 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 | } |