/src/pest/meta/src/validator.rs
Line | Count | Source |
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 | | |
10 | | //! Helpers for validating pest grammars that could help with debugging |
11 | | //! and provide a more user-friendly error message. |
12 | | |
13 | | use std::{ |
14 | | collections::{HashMap, HashSet}, |
15 | | sync::LazyLock, |
16 | | }; |
17 | | |
18 | | use pest::error::{Error, ErrorVariant, InputLocation}; |
19 | | use pest::iterators::Pairs; |
20 | | use pest::unicode::unicode_property_names; |
21 | | use pest::Span; |
22 | | |
23 | | use crate::parser::{ParserExpr, ParserNode, ParserRule, Rule}; |
24 | | |
25 | 0 | static RUST_KEYWORDS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| { |
26 | 0 | [ |
27 | 0 | "abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do", |
28 | 0 | "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop", |
29 | 0 | "macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pure", |
30 | 0 | "pub", "ref", "return", "Self", "self", "sizeof", "static", "struct", "super", "trait", |
31 | 0 | "true", "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield", |
32 | 0 | ] |
33 | 0 | .iter() |
34 | 0 | .cloned() |
35 | 0 | .collect() |
36 | 0 | }); |
37 | | |
38 | 0 | static PEST_KEYWORDS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| { |
39 | 0 | [ |
40 | 0 | "_", "ANY", "DROP", "EOI", "PEEK", "PEEK_ALL", "POP", "POP_ALL", "PUSH", "SOI", |
41 | 0 | ] |
42 | 0 | .iter() |
43 | 0 | .cloned() |
44 | 0 | .collect() |
45 | 0 | }); |
46 | | |
47 | 0 | static BUILTINS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| { |
48 | 0 | [ |
49 | 0 | "ANY", |
50 | 0 | "DROP", |
51 | 0 | "EOI", |
52 | 0 | "PEEK", |
53 | 0 | "PEEK_ALL", |
54 | 0 | "POP", |
55 | 0 | "POP_ALL", |
56 | 0 | "SOI", |
57 | 0 | "ASCII_DIGIT", |
58 | 0 | "ASCII_NONZERO_DIGIT", |
59 | 0 | "ASCII_BIN_DIGIT", |
60 | 0 | "ASCII_OCT_DIGIT", |
61 | 0 | "ASCII_HEX_DIGIT", |
62 | 0 | "ASCII_ALPHA_LOWER", |
63 | 0 | "ASCII_ALPHA_UPPER", |
64 | 0 | "ASCII_ALPHA", |
65 | 0 | "ASCII_ALPHANUMERIC", |
66 | 0 | "ASCII", |
67 | 0 | "NEWLINE", |
68 | 0 | ] |
69 | 0 | .iter() |
70 | 0 | .cloned() |
71 | 0 | .chain(unicode_property_names()) |
72 | 0 | .collect::<HashSet<&str>>() |
73 | 0 | }); |
74 | | |
75 | | /// It checks the parsed grammar for common mistakes: |
76 | | /// - using Pest keywords |
77 | | /// - duplicate rules |
78 | | /// - undefined rules |
79 | | /// |
80 | | /// It returns a `Result` with a `Vec` of `Error`s if any of the above is found. |
81 | | /// If no errors are found, it returns the vector of names of used builtin rules. |
82 | 0 | pub fn validate_pairs(pairs: Pairs<'_, Rule>) -> Result<Vec<&str>, Vec<Error<Rule>>> { |
83 | 0 | let definitions: Vec<_> = pairs |
84 | 0 | .clone() |
85 | 0 | .filter(|pair| pair.as_rule() == Rule::grammar_rule) |
86 | 0 | .map(|pair| pair.into_inner().next().unwrap()) |
87 | 0 | .filter(|pair| pair.as_rule() != Rule::line_doc) |
88 | 0 | .map(|pair| pair.as_span()) |
89 | 0 | .collect(); |
90 | | |
91 | 0 | let called_rules: Vec<_> = pairs |
92 | 0 | .clone() |
93 | 0 | .filter(|pair| pair.as_rule() == Rule::grammar_rule) |
94 | 0 | .flat_map(|pair| { |
95 | 0 | pair.into_inner() |
96 | 0 | .flatten() |
97 | 0 | .skip(1) |
98 | 0 | .filter(|pair| pair.as_rule() == Rule::identifier) |
99 | 0 | .map(|pair| pair.as_span()) |
100 | 0 | }) |
101 | 0 | .collect(); |
102 | | |
103 | 0 | let mut errors = vec![]; |
104 | | |
105 | 0 | errors.extend(validate_pest_keywords(&definitions)); |
106 | 0 | errors.extend(validate_already_defined(&definitions)); |
107 | 0 | errors.extend(validate_undefined(&definitions, &called_rules)); |
108 | | |
109 | 0 | if !errors.is_empty() { |
110 | 0 | return Err(errors); |
111 | 0 | } |
112 | | |
113 | 0 | let definitions: HashSet<_> = definitions.iter().map(|span| span.as_str()).collect(); |
114 | 0 | let called_rules: HashSet<_> = called_rules.iter().map(|span| span.as_str()).collect(); |
115 | | |
116 | 0 | let defaults = called_rules.difference(&definitions); |
117 | | |
118 | 0 | Ok(defaults.cloned().collect()) |
119 | 0 | } |
120 | | |
121 | | /// Validates that the given `definitions` do not contain any Rust keywords. |
122 | | #[allow(clippy::ptr_arg)] |
123 | | #[deprecated = "Rust keywords are no longer restricted from the pest grammar"] |
124 | 0 | pub fn validate_rust_keywords(definitions: &Vec<Span<'_>>) -> Vec<Error<Rule>> { |
125 | 0 | let mut errors = vec![]; |
126 | | |
127 | 0 | for definition in definitions { |
128 | 0 | let name = definition.as_str(); |
129 | | |
130 | 0 | if RUST_KEYWORDS.contains(name) { |
131 | 0 | errors.push(Error::new_from_span( |
132 | 0 | ErrorVariant::CustomError { |
133 | 0 | message: format!("{} is a rust keyword", name), |
134 | 0 | }, |
135 | 0 | *definition, |
136 | | )) |
137 | 0 | } |
138 | | } |
139 | | |
140 | 0 | errors |
141 | 0 | } |
142 | | |
143 | | /// Validates that the given `definitions` do not contain any Pest keywords. |
144 | | #[allow(clippy::ptr_arg)] |
145 | 0 | pub fn validate_pest_keywords(definitions: &Vec<Span<'_>>) -> Vec<Error<Rule>> { |
146 | 0 | let mut errors = vec![]; |
147 | | |
148 | 0 | for definition in definitions { |
149 | 0 | let name = definition.as_str(); |
150 | | |
151 | 0 | if PEST_KEYWORDS.contains(name) { |
152 | 0 | errors.push(Error::new_from_span( |
153 | 0 | ErrorVariant::CustomError { |
154 | 0 | message: format!("{} is a pest keyword", name), |
155 | 0 | }, |
156 | 0 | *definition, |
157 | | )) |
158 | 0 | } |
159 | | } |
160 | | |
161 | 0 | errors |
162 | 0 | } |
163 | | |
164 | | /// Validates that the given `definitions` do not contain any duplicate rules. |
165 | | #[allow(clippy::ptr_arg)] |
166 | 0 | pub fn validate_already_defined(definitions: &Vec<Span<'_>>) -> Vec<Error<Rule>> { |
167 | 0 | let mut errors = vec![]; |
168 | 0 | let mut defined = HashSet::new(); |
169 | | |
170 | 0 | for definition in definitions { |
171 | 0 | let name = definition.as_str(); |
172 | | |
173 | 0 | if defined.contains(&name) { |
174 | 0 | errors.push(Error::new_from_span( |
175 | 0 | ErrorVariant::CustomError { |
176 | 0 | message: format!("rule {} already defined", name), |
177 | 0 | }, |
178 | 0 | *definition, |
179 | | )) |
180 | 0 | } else { |
181 | 0 | defined.insert(name); |
182 | 0 | } |
183 | | } |
184 | | |
185 | 0 | errors |
186 | 0 | } |
187 | | |
188 | | /// Validates that the given `definitions` do not contain any undefined rules. |
189 | | #[allow(clippy::ptr_arg)] |
190 | 0 | pub fn validate_undefined<'i>( |
191 | 0 | definitions: &Vec<Span<'i>>, |
192 | 0 | called_rules: &Vec<Span<'i>>, |
193 | 0 | ) -> Vec<Error<Rule>> { |
194 | 0 | let mut errors = vec![]; |
195 | 0 | let definitions: HashSet<_> = definitions.iter().map(|span| span.as_str()).collect(); |
196 | | |
197 | 0 | for rule in called_rules { |
198 | 0 | let name = rule.as_str(); |
199 | | |
200 | 0 | if !definitions.contains(name) && !BUILTINS.contains(name) { |
201 | 0 | errors.push(Error::new_from_span( |
202 | 0 | ErrorVariant::CustomError { |
203 | 0 | message: format!("rule {} is undefined", name), |
204 | 0 | }, |
205 | 0 | *rule, |
206 | | )) |
207 | 0 | } |
208 | | } |
209 | | |
210 | 0 | errors |
211 | 0 | } |
212 | | |
213 | | /// Validates the abstract syntax tree for common mistakes: |
214 | | /// - infinite repetitions |
215 | | /// - choices that cannot be reached |
216 | | /// - left recursion |
217 | | #[allow(clippy::ptr_arg)] |
218 | 0 | pub fn validate_ast<'a, 'i: 'a>(rules: &'a Vec<ParserRule<'i>>) -> Vec<Error<Rule>> { |
219 | 0 | let mut errors = vec![]; |
220 | | |
221 | | // WARNING: validate_{repetition,choice,whitespace_comment} |
222 | | // use is_non_failing and is_non_progressing breaking assumptions: |
223 | | // - for every `ParserExpr::RepMinMax(inner,min,max)`, |
224 | | // `min<=max` was not checked |
225 | | // - left recursion was not checked |
226 | | // - Every expression might not be checked |
227 | 0 | errors.extend(validate_repetition(rules)); |
228 | 0 | errors.extend(validate_choices(rules)); |
229 | 0 | errors.extend(validate_whitespace_comment(rules)); |
230 | 0 | errors.extend(validate_left_recursion(rules)); |
231 | | #[cfg(feature = "grammar-extras")] |
232 | | errors.extend(validate_tag_silent_rules(rules)); |
233 | | |
234 | 0 | errors.sort_by_key(|error| match error.location { |
235 | 0 | InputLocation::Span(span) => span, |
236 | 0 | _ => unreachable!(), |
237 | 0 | }); |
238 | | |
239 | 0 | errors |
240 | 0 | } |
241 | | |
242 | | #[cfg(feature = "grammar-extras")] |
243 | | fn validate_tag_silent_rules<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> { |
244 | | use crate::ast::RuleType; |
245 | | |
246 | | fn to_type_hash_map<'a, 'i: 'a>( |
247 | | rules: &'a [ParserRule<'i>], |
248 | | ) -> HashMap<String, (&'a ParserNode<'i>, RuleType)> { |
249 | | rules |
250 | | .iter() |
251 | | .map(|r| (r.name.clone(), (&r.node, r.ty))) |
252 | | .collect() |
253 | | } |
254 | | let mut result = vec![]; |
255 | | |
256 | | fn check_silent_builtin<'a, 'i: 'a>( |
257 | | expr: &ParserExpr<'i>, |
258 | | rules_ref: &HashMap<String, (&'a ParserNode<'i>, RuleType)>, |
259 | | span: Span<'a>, |
260 | | ) -> Option<Error<Rule>> { |
261 | | match &expr { |
262 | | ParserExpr::Ident(rule_name) => { |
263 | | let rule = rules_ref.get(rule_name); |
264 | | if matches!(rule, Some((_, RuleType::Silent))) { |
265 | | return Some(Error::<Rule>::new_from_span( |
266 | | ErrorVariant::CustomError { |
267 | | message: "tags on silent rules will not appear in the output" |
268 | | .to_owned(), |
269 | | }, |
270 | | span, |
271 | | )); |
272 | | } else if BUILTINS.contains(rule_name.as_str()) { |
273 | | return Some(Error::new_from_span( |
274 | | ErrorVariant::CustomError { |
275 | | message: "tags on built-in rules will not appear in the output" |
276 | | .to_owned(), |
277 | | }, |
278 | | span, |
279 | | )); |
280 | | } |
281 | | } |
282 | | ParserExpr::Rep(node) |
283 | | | ParserExpr::RepMinMax(node, _, _) |
284 | | | ParserExpr::RepMax(node, _) |
285 | | | ParserExpr::RepMin(node, _) |
286 | | | ParserExpr::RepOnce(node) |
287 | | | ParserExpr::RepExact(node, _) |
288 | | | ParserExpr::Opt(node) |
289 | | | ParserExpr::Push(node) |
290 | | | ParserExpr::PosPred(node) |
291 | | | ParserExpr::NegPred(node) => { |
292 | | return check_silent_builtin(&node.expr, rules_ref, span); |
293 | | } |
294 | | _ => {} |
295 | | }; |
296 | | None |
297 | | } |
298 | | |
299 | | let rules_map = to_type_hash_map(rules); |
300 | | for rule in rules { |
301 | | let rules_ref = &rules_map; |
302 | | let mut errors = rule.node.clone().filter_map_top_down(|node1| { |
303 | | if let ParserExpr::NodeTag(node2, _) = node1.expr { |
304 | | check_silent_builtin(&node2.expr, rules_ref, node1.span) |
305 | | } else { |
306 | | None |
307 | | } |
308 | | }); |
309 | | result.append(&mut errors); |
310 | | } |
311 | | result |
312 | | } |
313 | | |
314 | | /// Checks if `expr` is non-progressing, that is the expression does not |
315 | | /// consume any input or any stack. This includes expressions matching the empty input, |
316 | | /// `SOI` and ̀ `EOI`, predicates and repetitions. |
317 | | /// |
318 | | /// # Example |
319 | | /// |
320 | | /// ```pest |
321 | | /// not_progressing_1 = { "" } |
322 | | /// not_progressing_2 = { "a"? } |
323 | | /// not_progressing_3 = { !"a" } |
324 | | /// ``` |
325 | | /// |
326 | | /// # Assumptions |
327 | | /// - In `ParserExpr::RepMinMax(inner,min,max)`, `min<=max` |
328 | | /// - All rules identiers have a matching definition |
329 | | /// - There is no left-recursion (if only this one is broken returns false) |
330 | | /// - Every expression is being checked |
331 | 0 | fn is_non_progressing<'i>( |
332 | 0 | expr: &ParserExpr<'i>, |
333 | 0 | rules: &HashMap<String, &ParserNode<'i>>, |
334 | 0 | trace: &mut Vec<String>, |
335 | 0 | ) -> bool { |
336 | 0 | match *expr { |
337 | 0 | ParserExpr::Str(ref string) | ParserExpr::Insens(ref string) => string.is_empty(), |
338 | 0 | ParserExpr::Ident(ref ident) => { |
339 | 0 | if ident == "SOI" || ident == "EOI" { |
340 | 0 | return true; |
341 | 0 | } |
342 | | |
343 | 0 | if !trace.contains(ident) { |
344 | 0 | if let Some(node) = rules.get(ident) { |
345 | 0 | trace.push(ident.clone()); |
346 | 0 | let result = is_non_progressing(&node.expr, rules, trace); |
347 | 0 | trace.pop().unwrap(); |
348 | | |
349 | 0 | return result; |
350 | 0 | } |
351 | | // else |
352 | | // the ident is |
353 | | // - "POP","PEEK" => false |
354 | | // the slice being checked is not non_progressing since every |
355 | | // PUSH is being checked (assumption 4) and the expr |
356 | | // of a PUSH has to be non_progressing. |
357 | | // - "POPALL", "PEEKALL" => false |
358 | | // same as "POP", "PEEK" unless the following: |
359 | | // BUG: if the stack is empty they are non_progressing |
360 | | // - "DROP" => false doesn't consume the input but consumes the stack, |
361 | | // - "ANY", "ASCII_*", UNICODE categories, "NEWLINE" => false |
362 | | // - referring to another rule that is undefined (breaks assumption) |
363 | 0 | } |
364 | | // else referring to another rule that was already seen. |
365 | | // this happens only if there is a left-recursion |
366 | | // that is only if an assumption is broken, |
367 | | // WARNING: we can choose to return false, but that might |
368 | | // cause bugs into the left_recursion check |
369 | | |
370 | 0 | false |
371 | | } |
372 | 0 | ParserExpr::Seq(ref lhs, ref rhs) => { |
373 | 0 | is_non_progressing(&lhs.expr, rules, trace) |
374 | 0 | && is_non_progressing(&rhs.expr, rules, trace) |
375 | | } |
376 | 0 | ParserExpr::Choice(ref lhs, ref rhs) => { |
377 | 0 | is_non_progressing(&lhs.expr, rules, trace) |
378 | 0 | || is_non_progressing(&rhs.expr, rules, trace) |
379 | | } |
380 | | // WARNING: the predicate indeed won't make progress on input but it |
381 | | // might progress on the stack |
382 | | // ex: @{ PUSH(ANY) ~ (&(DROP))* ~ ANY }, input="AA" |
383 | | // Notice that this is ex not working as of now, the debugger seems |
384 | | // to run into an infinite loop on it |
385 | 0 | ParserExpr::PosPred(_) | ParserExpr::NegPred(_) => true, |
386 | 0 | ParserExpr::Rep(_) | ParserExpr::Opt(_) | ParserExpr::RepMax(_, _) => true, |
387 | | // it either always fail (failing is progressing) |
388 | | // or always match at least a character |
389 | 0 | ParserExpr::Range(_, _) => false, |
390 | | ParserExpr::PeekSlice(_, _) => { |
391 | | // the slice being checked is not non_progressing since every |
392 | | // PUSH is being checked (assumption 4) and the expr |
393 | | // of a PUSH has to be non_progressing. |
394 | | // BUG: if the slice is of size 0, or the stack is not large |
395 | | // enough it might be non-progressing |
396 | 0 | false |
397 | | } |
398 | | |
399 | 0 | ParserExpr::RepExact(ref inner, min) |
400 | 0 | | ParserExpr::RepMin(ref inner, min) |
401 | 0 | | ParserExpr::RepMinMax(ref inner, min, _) => { |
402 | 0 | min == 0 || is_non_progressing(&inner.expr, rules, trace) |
403 | | } |
404 | 0 | ParserExpr::Push(ref inner) => is_non_progressing(&inner.expr, rules, trace), |
405 | | #[cfg(feature = "grammar-extras")] |
406 | | ParserExpr::PushLiteral(_) => true, |
407 | 0 | ParserExpr::RepOnce(ref inner) => is_non_progressing(&inner.expr, rules, trace), |
408 | | #[cfg(feature = "grammar-extras")] |
409 | | ParserExpr::NodeTag(ref inner, _) => is_non_progressing(&inner.expr, rules, trace), |
410 | | } |
411 | 0 | } |
412 | | |
413 | | /// Checks if `expr` is non-failing, that is it matches any input. |
414 | | /// |
415 | | /// # Example |
416 | | /// |
417 | | /// ```pest |
418 | | /// non_failing_1 = { "" } |
419 | | /// ``` |
420 | | /// |
421 | | /// # Assumptions |
422 | | /// - In `ParserExpr::RepMinMax(inner,min,max)`, `min<=max` |
423 | | /// - In `ParserExpr::PeekSlice(max,Some(min))`, `max>=min` |
424 | | /// - All rules identiers have a matching definition |
425 | | /// - There is no left-recursion |
426 | | /// - All rules are being checked |
427 | 0 | fn is_non_failing<'i>( |
428 | 0 | expr: &ParserExpr<'i>, |
429 | 0 | rules: &HashMap<String, &ParserNode<'i>>, |
430 | 0 | trace: &mut Vec<String>, |
431 | 0 | ) -> bool { |
432 | 0 | match *expr { |
433 | 0 | ParserExpr::Str(ref string) | ParserExpr::Insens(ref string) => string.is_empty(), |
434 | 0 | ParserExpr::Ident(ref ident) => { |
435 | 0 | if !trace.contains(ident) { |
436 | 0 | if let Some(node) = rules.get(ident) { |
437 | 0 | trace.push(ident.clone()); |
438 | 0 | let result = is_non_failing(&node.expr, rules, trace); |
439 | 0 | trace.pop().unwrap(); |
440 | | |
441 | 0 | result |
442 | | } else { |
443 | | // else |
444 | | // the ident is |
445 | | // - "POP","PEEK" => false |
446 | | // the slice being checked is not non_failing since every |
447 | | // PUSH is being checked (assumption 4) and the expr |
448 | | // of a PUSH has to be non_failing. |
449 | | // - "POP_ALL", "PEEK_ALL" => false |
450 | | // same as "POP", "PEEK" unless the following: |
451 | | // BUG: if the stack is empty they are non_failing |
452 | | // - "DROP" => false |
453 | | // - "ANY", "ASCII_*", UNICODE categories, "NEWLINE", |
454 | | // "SOI", "EOI" => false |
455 | | // - referring to another rule that is undefined (breaks assumption) |
456 | | // WARNING: might want to introduce a panic or report the error |
457 | 0 | false |
458 | | } |
459 | | } else { |
460 | | // referring to another rule R that was already seen |
461 | | // WARNING: this might mean there is a circular non-failing path |
462 | | // it's not obvious wether this can happen without left-recursion |
463 | | // and thus breaking the assumption. Until there is answer to |
464 | | // this, to avoid changing behaviour we return: |
465 | 0 | false |
466 | | } |
467 | | } |
468 | 0 | ParserExpr::Opt(_) => true, |
469 | 0 | ParserExpr::Rep(_) => true, |
470 | 0 | ParserExpr::RepMax(_, _) => true, |
471 | 0 | ParserExpr::Seq(ref lhs, ref rhs) => { |
472 | 0 | is_non_failing(&lhs.expr, rules, trace) && is_non_failing(&rhs.expr, rules, trace) |
473 | | } |
474 | 0 | ParserExpr::Choice(ref lhs, ref rhs) => { |
475 | 0 | is_non_failing(&lhs.expr, rules, trace) || is_non_failing(&rhs.expr, rules, trace) |
476 | | } |
477 | | // it either always fail |
478 | | // or always match at least a character |
479 | 0 | ParserExpr::Range(_, _) => false, |
480 | | ParserExpr::PeekSlice(_, _) => { |
481 | | // the slice being checked is not non_failing since every |
482 | | // PUSH is being checked (assumption 4) and the expr |
483 | | // of a PUSH has to be non_failing. |
484 | | // BUG: if the slice is of size 0, or the stack is not large |
485 | | // enough it might be non-failing |
486 | 0 | false |
487 | | } |
488 | 0 | ParserExpr::RepExact(ref inner, min) |
489 | 0 | | ParserExpr::RepMin(ref inner, min) |
490 | 0 | | ParserExpr::RepMinMax(ref inner, min, _) => { |
491 | 0 | min == 0 || is_non_failing(&inner.expr, rules, trace) |
492 | | } |
493 | | // BUG: the predicate may always fail, resulting in this expr non_failing |
494 | | // ex of always failing predicates : |
495 | | // @{EOI ~ ANY | ANY ~ SOI | &("A") ~ &("B") | 'z'..'a'} |
496 | 0 | ParserExpr::NegPred(_) => false, |
497 | 0 | ParserExpr::RepOnce(ref inner) => is_non_failing(&inner.expr, rules, trace), |
498 | 0 | ParserExpr::Push(ref inner) | ParserExpr::PosPred(ref inner) => { |
499 | 0 | is_non_failing(&inner.expr, rules, trace) |
500 | | } |
501 | | #[cfg(feature = "grammar-extras")] |
502 | | ParserExpr::PushLiteral(_) => true, |
503 | | #[cfg(feature = "grammar-extras")] |
504 | | ParserExpr::NodeTag(ref inner, _) => is_non_failing(&inner.expr, rules, trace), |
505 | | } |
506 | 0 | } |
507 | | |
508 | 0 | fn validate_repetition<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> { |
509 | 0 | let mut result = vec![]; |
510 | 0 | let map = to_hash_map(rules); |
511 | | |
512 | 0 | for rule in rules { |
513 | 0 | let mut errors = rule.node |
514 | 0 | .clone() |
515 | 0 | .filter_map_top_down(|node| match node.expr { |
516 | 0 | ParserExpr::Rep(ref other) |
517 | 0 | | ParserExpr::RepOnce(ref other) |
518 | 0 | | ParserExpr::RepMin(ref other, _) => { |
519 | 0 | if is_non_failing(&other.expr, &map, &mut vec![]) { |
520 | 0 | Some(Error::new_from_span( |
521 | 0 | ErrorVariant::CustomError { |
522 | 0 | message: |
523 | 0 | "expression inside repetition cannot fail and will repeat \ |
524 | 0 | infinitely" |
525 | 0 | .to_owned() |
526 | 0 | }, |
527 | 0 | node.span |
528 | 0 | )) |
529 | 0 | } else if is_non_progressing(&other.expr, &map, &mut vec![]) { |
530 | 0 | Some(Error::new_from_span( |
531 | 0 | ErrorVariant::CustomError { |
532 | 0 | message: |
533 | 0 | "expression inside repetition is non-progressing and will repeat \ |
534 | 0 | infinitely" |
535 | 0 | .to_owned(), |
536 | 0 | }, |
537 | 0 | node.span |
538 | 0 | )) |
539 | | } else { |
540 | 0 | None |
541 | | } |
542 | | } |
543 | 0 | _ => None |
544 | 0 | }); |
545 | | |
546 | 0 | result.append(&mut errors); |
547 | | } |
548 | | |
549 | 0 | result |
550 | 0 | } |
551 | | |
552 | 0 | fn validate_choices<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> { |
553 | 0 | let mut result = vec![]; |
554 | 0 | let map = to_hash_map(rules); |
555 | | |
556 | 0 | for rule in rules { |
557 | 0 | let mut errors = rule |
558 | 0 | .node |
559 | 0 | .clone() |
560 | 0 | .filter_map_top_down(|node| match node.expr { |
561 | 0 | ParserExpr::Choice(ref lhs, _) => { |
562 | 0 | let node = match lhs.expr { |
563 | 0 | ParserExpr::Choice(_, ref rhs) => rhs, |
564 | 0 | _ => lhs, |
565 | | }; |
566 | | |
567 | 0 | if is_non_failing(&node.expr, &map, &mut vec![]) { |
568 | 0 | Some(Error::new_from_span( |
569 | 0 | ErrorVariant::CustomError { |
570 | 0 | message: |
571 | 0 | "expression cannot fail; following choices cannot be reached" |
572 | 0 | .to_owned(), |
573 | 0 | }, |
574 | 0 | node.span, |
575 | 0 | )) |
576 | | } else { |
577 | 0 | None |
578 | | } |
579 | | } |
580 | 0 | _ => None, |
581 | 0 | }); |
582 | | |
583 | 0 | result.append(&mut errors); |
584 | | } |
585 | | |
586 | 0 | result |
587 | 0 | } |
588 | | |
589 | 0 | fn validate_whitespace_comment<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> { |
590 | 0 | let map = to_hash_map(rules); |
591 | | |
592 | 0 | rules |
593 | 0 | .iter() |
594 | 0 | .filter_map(|rule| { |
595 | 0 | if rule.name == "WHITESPACE" || rule.name == "COMMENT" { |
596 | 0 | if is_non_failing(&rule.node.expr, &map, &mut vec![]) { |
597 | 0 | Some(Error::new_from_span( |
598 | 0 | ErrorVariant::CustomError { |
599 | 0 | message: format!( |
600 | 0 | "{} cannot fail and will repeat infinitely", |
601 | 0 | &rule.name |
602 | 0 | ), |
603 | 0 | }, |
604 | 0 | rule.node.span, |
605 | 0 | )) |
606 | 0 | } else if is_non_progressing(&rule.node.expr, &map, &mut vec![]) { |
607 | 0 | Some(Error::new_from_span( |
608 | 0 | ErrorVariant::CustomError { |
609 | 0 | message: format!( |
610 | 0 | "{} is non-progressing and will repeat infinitely", |
611 | 0 | &rule.name |
612 | 0 | ), |
613 | 0 | }, |
614 | 0 | rule.node.span, |
615 | 0 | )) |
616 | | } else { |
617 | 0 | None |
618 | | } |
619 | | } else { |
620 | 0 | None |
621 | | } |
622 | 0 | }) |
623 | 0 | .collect() |
624 | 0 | } |
625 | | |
626 | 0 | fn validate_left_recursion<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec<Error<Rule>> { |
627 | 0 | left_recursion(to_hash_map(rules)) |
628 | 0 | } |
629 | | |
630 | 0 | fn to_hash_map<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> HashMap<String, &'a ParserNode<'i>> { |
631 | 0 | rules.iter().map(|r| (r.name.clone(), &r.node)).collect() |
632 | 0 | } |
633 | | |
634 | 0 | fn left_recursion<'a, 'i: 'a>(rules: HashMap<String, &'a ParserNode<'i>>) -> Vec<Error<Rule>> { |
635 | 0 | fn check_expr<'a, 'i: 'a>( |
636 | 0 | node: &'a ParserNode<'i>, |
637 | 0 | rules: &'a HashMap<String, &ParserNode<'i>>, |
638 | 0 | trace: &mut Vec<String>, |
639 | 0 | ) -> Option<Error<Rule>> { |
640 | 0 | match node.expr.clone() { |
641 | 0 | ParserExpr::Ident(other) => { |
642 | 0 | if trace[0] == other { |
643 | 0 | trace.push(other); |
644 | 0 | let chain = trace |
645 | 0 | .iter() |
646 | 0 | .map(|ident| ident.as_ref()) |
647 | 0 | .collect::<Vec<_>>() |
648 | 0 | .join(" -> "); |
649 | | |
650 | 0 | return Some(Error::new_from_span( |
651 | 0 | ErrorVariant::CustomError { |
652 | 0 | message: format!( |
653 | 0 | "rule {} is left-recursive ({}); pest::pratt_parser might be useful \ |
654 | 0 | in this case", |
655 | 0 | node.span.as_str(), |
656 | 0 | chain |
657 | 0 | ) |
658 | 0 | }, |
659 | 0 | node.span |
660 | 0 | )); |
661 | 0 | } |
662 | | |
663 | 0 | if !trace.contains(&other) { |
664 | 0 | if let Some(node) = rules.get(&other) { |
665 | 0 | trace.push(other); |
666 | 0 | let result = check_expr(node, rules, trace); |
667 | 0 | trace.pop().unwrap(); |
668 | | |
669 | 0 | return result; |
670 | 0 | } |
671 | 0 | } |
672 | | |
673 | 0 | None |
674 | | } |
675 | 0 | ParserExpr::Seq(ref lhs, ref rhs) => { |
676 | 0 | if is_non_failing(&lhs.expr, rules, &mut vec![trace.last().unwrap().clone()]) |
677 | 0 | || is_non_progressing( |
678 | 0 | &lhs.expr, |
679 | 0 | rules, |
680 | 0 | &mut vec![trace.last().unwrap().clone()], |
681 | | ) |
682 | | { |
683 | 0 | check_expr(rhs, rules, trace) |
684 | | } else { |
685 | 0 | check_expr(lhs, rules, trace) |
686 | | } |
687 | | } |
688 | 0 | ParserExpr::Choice(ref lhs, ref rhs) => { |
689 | 0 | check_expr(lhs, rules, trace).or_else(|| check_expr(rhs, rules, trace)) |
690 | | } |
691 | 0 | ParserExpr::Rep(ref node) => check_expr(node, rules, trace), |
692 | 0 | ParserExpr::RepOnce(ref node) => check_expr(node, rules, trace), |
693 | 0 | ParserExpr::Opt(ref node) => check_expr(node, rules, trace), |
694 | 0 | ParserExpr::PosPred(ref node) => check_expr(node, rules, trace), |
695 | 0 | ParserExpr::NegPred(ref node) => check_expr(node, rules, trace), |
696 | 0 | ParserExpr::Push(ref node) => check_expr(node, rules, trace), |
697 | 0 | _ => None, |
698 | | } |
699 | 0 | } |
700 | | |
701 | 0 | let mut errors = vec![]; |
702 | | |
703 | 0 | for (name, node) in &rules { |
704 | 0 | let name = name.clone(); |
705 | | |
706 | 0 | if let Some(error) = check_expr(node, &rules, &mut vec![name]) { |
707 | 0 | errors.push(error); |
708 | 0 | } |
709 | | } |
710 | | |
711 | 0 | errors |
712 | 0 | } |
713 | | |
714 | | #[cfg(test)] |
715 | | mod tests { |
716 | | use super::super::parser::{consume_rules, PestParser}; |
717 | | use super::super::unwrap_or_report; |
718 | | use super::*; |
719 | | use pest::Parser; |
720 | | |
721 | | #[test] |
722 | | #[should_panic(expected = "grammar error |
723 | | |
724 | | --> 1:1 |
725 | | | |
726 | | 1 | ANY = { \"a\" } |
727 | | | ^-^ |
728 | | | |
729 | | = ANY is a pest keyword")] |
730 | | fn pest_keyword() { |
731 | | let input = "ANY = { \"a\" }"; |
732 | | unwrap_or_report(validate_pairs( |
733 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
734 | | )); |
735 | | } |
736 | | |
737 | | #[test] |
738 | | #[should_panic(expected = "grammar error |
739 | | |
740 | | --> 1:13 |
741 | | | |
742 | | 1 | a = { \"a\" } a = { \"a\" } |
743 | | | ^ |
744 | | | |
745 | | = rule a already defined")] |
746 | | fn already_defined() { |
747 | | let input = "a = { \"a\" } a = { \"a\" }"; |
748 | | unwrap_or_report(validate_pairs( |
749 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
750 | | )); |
751 | | } |
752 | | |
753 | | #[test] |
754 | | #[should_panic(expected = "grammar error |
755 | | |
756 | | --> 1:7 |
757 | | | |
758 | | 1 | a = { b } |
759 | | | ^ |
760 | | | |
761 | | = rule b is undefined")] |
762 | | fn undefined() { |
763 | | let input = "a = { b }"; |
764 | | unwrap_or_report(validate_pairs( |
765 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
766 | | )); |
767 | | } |
768 | | |
769 | | #[test] |
770 | | fn valid_recursion() { |
771 | | let input = "a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"b\") ~ a }"; |
772 | | unwrap_or_report(consume_rules( |
773 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
774 | | )); |
775 | | } |
776 | | |
777 | | #[test] |
778 | | #[should_panic(expected = "grammar error |
779 | | |
780 | | --> 1:16 |
781 | | | |
782 | | 1 | WHITESPACE = { \"\" } |
783 | | | ^^ |
784 | | | |
785 | | = WHITESPACE cannot fail and will repeat infinitely")] |
786 | | fn non_failing_whitespace() { |
787 | | let input = "WHITESPACE = { \"\" }"; |
788 | | unwrap_or_report(consume_rules( |
789 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
790 | | )); |
791 | | } |
792 | | |
793 | | #[test] |
794 | | #[should_panic(expected = "grammar error |
795 | | |
796 | | --> 1:13 |
797 | | | |
798 | | 1 | COMMENT = { SOI } |
799 | | | ^-^ |
800 | | | |
801 | | = COMMENT is non-progressing and will repeat infinitely")] |
802 | | fn non_progressing_comment() { |
803 | | let input = "COMMENT = { SOI }"; |
804 | | unwrap_or_report(consume_rules( |
805 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
806 | | )); |
807 | | } |
808 | | |
809 | | #[test] |
810 | | fn non_progressing_empty_string() { |
811 | | assert!(is_non_failing( |
812 | | &ParserExpr::Insens("".into()), |
813 | | &HashMap::new(), |
814 | | &mut Vec::new() |
815 | | )); |
816 | | assert!(is_non_progressing( |
817 | | &ParserExpr::Str("".into()), |
818 | | &HashMap::new(), |
819 | | &mut Vec::new() |
820 | | )); |
821 | | } |
822 | | |
823 | | #[test] |
824 | | fn progressing_non_empty_string() { |
825 | | assert!(!is_non_progressing( |
826 | | &ParserExpr::Insens("non empty".into()), |
827 | | &HashMap::new(), |
828 | | &mut Vec::new() |
829 | | )); |
830 | | assert!(!is_non_progressing( |
831 | | &ParserExpr::Str("non empty".into()), |
832 | | &HashMap::new(), |
833 | | &mut Vec::new() |
834 | | )); |
835 | | } |
836 | | |
837 | | #[test] |
838 | | fn non_progressing_soi_eoi() { |
839 | | assert!(is_non_progressing( |
840 | | &ParserExpr::Ident("SOI".into()), |
841 | | &HashMap::new(), |
842 | | &mut Vec::new() |
843 | | )); |
844 | | assert!(is_non_progressing( |
845 | | &ParserExpr::Ident("EOI".into()), |
846 | | &HashMap::new(), |
847 | | &mut Vec::new() |
848 | | )); |
849 | | } |
850 | | |
851 | | #[test] |
852 | | fn non_progressing_predicates() { |
853 | | let progressing = ParserExpr::Str("A".into()); |
854 | | |
855 | | assert!(is_non_progressing( |
856 | | &ParserExpr::PosPred(Box::new(ParserNode { |
857 | | expr: progressing.clone(), |
858 | | span: Span::new(" ", 0, 1).unwrap(), |
859 | | })), |
860 | | &HashMap::new(), |
861 | | &mut Vec::new() |
862 | | )); |
863 | | assert!(is_non_progressing( |
864 | | &ParserExpr::NegPred(Box::new(ParserNode { |
865 | | expr: progressing, |
866 | | span: Span::new(" ", 0, 1).unwrap(), |
867 | | })), |
868 | | &HashMap::new(), |
869 | | &mut Vec::new() |
870 | | )); |
871 | | } |
872 | | |
873 | | #[test] |
874 | | fn non_progressing_0_length_repetitions() { |
875 | | let input_progressing_node = Box::new(ParserNode { |
876 | | expr: ParserExpr::Str("A".into()), |
877 | | span: Span::new(" ", 0, 1).unwrap(), |
878 | | }); |
879 | | |
880 | | assert!(!is_non_progressing( |
881 | | &input_progressing_node.clone().expr, |
882 | | &HashMap::new(), |
883 | | &mut Vec::new() |
884 | | )); |
885 | | |
886 | | assert!(is_non_progressing( |
887 | | &ParserExpr::Rep(input_progressing_node.clone()), |
888 | | &HashMap::new(), |
889 | | &mut Vec::new() |
890 | | )); |
891 | | assert!(is_non_progressing( |
892 | | &ParserExpr::Opt(input_progressing_node.clone()), |
893 | | &HashMap::new(), |
894 | | &mut Vec::new() |
895 | | )); |
896 | | assert!(is_non_progressing( |
897 | | &ParserExpr::RepExact(input_progressing_node.clone(), 0), |
898 | | &HashMap::new(), |
899 | | &mut Vec::new() |
900 | | )); |
901 | | assert!(is_non_progressing( |
902 | | &ParserExpr::RepMin(input_progressing_node.clone(), 0), |
903 | | &HashMap::new(), |
904 | | &mut Vec::new() |
905 | | )); |
906 | | assert!(is_non_progressing( |
907 | | &ParserExpr::RepMax(input_progressing_node.clone(), 0), |
908 | | &HashMap::new(), |
909 | | &mut Vec::new() |
910 | | )); |
911 | | assert!(is_non_progressing( |
912 | | &ParserExpr::RepMax(input_progressing_node.clone(), 17), |
913 | | &HashMap::new(), |
914 | | &mut Vec::new() |
915 | | )); |
916 | | |
917 | | assert!(is_non_progressing( |
918 | | &ParserExpr::RepMinMax(input_progressing_node.clone(), 0, 12), |
919 | | &HashMap::new(), |
920 | | &mut Vec::new() |
921 | | )); |
922 | | } |
923 | | |
924 | | #[test] |
925 | | fn non_progressing_nonzero_repetitions_with_non_progressing_expr() { |
926 | | let a = ""; |
927 | | let non_progressing_node = Box::new(ParserNode { |
928 | | expr: ParserExpr::Str(a.into()), |
929 | | span: Span::new(a, 0, 0).unwrap(), |
930 | | }); |
931 | | let exact = ParserExpr::RepExact(non_progressing_node.clone(), 7); |
932 | | let min = ParserExpr::RepMin(non_progressing_node.clone(), 23); |
933 | | let minmax = ParserExpr::RepMinMax(non_progressing_node.clone(), 12, 13); |
934 | | let reponce = ParserExpr::RepOnce(non_progressing_node); |
935 | | |
936 | | assert!(is_non_progressing(&exact, &HashMap::new(), &mut Vec::new())); |
937 | | assert!(is_non_progressing(&min, &HashMap::new(), &mut Vec::new())); |
938 | | assert!(is_non_progressing( |
939 | | &minmax, |
940 | | &HashMap::new(), |
941 | | &mut Vec::new() |
942 | | )); |
943 | | assert!(is_non_progressing( |
944 | | &reponce, |
945 | | &HashMap::new(), |
946 | | &mut Vec::new() |
947 | | )); |
948 | | } |
949 | | |
950 | | #[test] |
951 | | fn progressing_repetitions() { |
952 | | let a = "A"; |
953 | | let input_progressing_node = Box::new(ParserNode { |
954 | | expr: ParserExpr::Str(a.into()), |
955 | | span: Span::new(a, 0, 1).unwrap(), |
956 | | }); |
957 | | let exact = ParserExpr::RepExact(input_progressing_node.clone(), 1); |
958 | | let min = ParserExpr::RepMin(input_progressing_node.clone(), 2); |
959 | | let minmax = ParserExpr::RepMinMax(input_progressing_node.clone(), 4, 5); |
960 | | let reponce = ParserExpr::RepOnce(input_progressing_node); |
961 | | |
962 | | assert!(!is_non_progressing( |
963 | | &exact, |
964 | | &HashMap::new(), |
965 | | &mut Vec::new() |
966 | | )); |
967 | | assert!(!is_non_progressing(&min, &HashMap::new(), &mut Vec::new())); |
968 | | assert!(!is_non_progressing( |
969 | | &minmax, |
970 | | &HashMap::new(), |
971 | | &mut Vec::new() |
972 | | )); |
973 | | assert!(!is_non_progressing( |
974 | | &reponce, |
975 | | &HashMap::new(), |
976 | | &mut Vec::new() |
977 | | )); |
978 | | } |
979 | | |
980 | | #[test] |
981 | | fn non_progressing_push() { |
982 | | let a = ""; |
983 | | let non_progressing_node = Box::new(ParserNode { |
984 | | expr: ParserExpr::Str(a.into()), |
985 | | span: Span::new(a, 0, 0).unwrap(), |
986 | | }); |
987 | | let push = ParserExpr::Push(non_progressing_node.clone()); |
988 | | |
989 | | assert!(is_non_progressing(&push, &HashMap::new(), &mut Vec::new())); |
990 | | } |
991 | | |
992 | | #[test] |
993 | | fn progressing_push() { |
994 | | let a = "i'm make progress"; |
995 | | let progressing_node = Box::new(ParserNode { |
996 | | expr: ParserExpr::Str(a.into()), |
997 | | span: Span::new(a, 0, 1).unwrap(), |
998 | | }); |
999 | | let push = ParserExpr::Push(progressing_node.clone()); |
1000 | | |
1001 | | assert!(!is_non_progressing(&push, &HashMap::new(), &mut Vec::new())); |
1002 | | } |
1003 | | |
1004 | | #[cfg(feature = "grammar-extras")] |
1005 | | #[test] |
1006 | | fn push_literal_is_non_progressing() { |
1007 | | let a = ""; |
1008 | | let non_progressing_node = Box::new(ParserNode { |
1009 | | expr: ParserExpr::PushLiteral("a".to_string()), |
1010 | | span: Span::new(a, 0, 0).unwrap(), |
1011 | | }); |
1012 | | let push = ParserExpr::Push(non_progressing_node.clone()); |
1013 | | |
1014 | | assert!(is_non_progressing(&push, &HashMap::new(), &mut Vec::new())); |
1015 | | } |
1016 | | |
1017 | | #[test] |
1018 | | fn node_tag_forwards_is_non_progressing() { |
1019 | | let progressing_node = Box::new(ParserNode { |
1020 | | expr: ParserExpr::Str("i'm make progress".into()), |
1021 | | span: Span::new(" ", 0, 1).unwrap(), |
1022 | | }); |
1023 | | assert!(!is_non_progressing( |
1024 | | &progressing_node.clone().expr, |
1025 | | &HashMap::new(), |
1026 | | &mut Vec::new() |
1027 | | )); |
1028 | | let non_progressing_node = Box::new(ParserNode { |
1029 | | expr: ParserExpr::Str("".into()), |
1030 | | span: Span::new(" ", 0, 1).unwrap(), |
1031 | | }); |
1032 | | assert!(is_non_progressing( |
1033 | | &non_progressing_node.clone().expr, |
1034 | | &HashMap::new(), |
1035 | | &mut Vec::new() |
1036 | | )); |
1037 | | #[cfg(feature = "grammar-extras")] |
1038 | | { |
1039 | | let progressing = ParserExpr::NodeTag(progressing_node.clone(), "TAG".into()); |
1040 | | let non_progressing = ParserExpr::NodeTag(non_progressing_node.clone(), "TAG".into()); |
1041 | | |
1042 | | assert!(!is_non_progressing( |
1043 | | &progressing, |
1044 | | &HashMap::new(), |
1045 | | &mut Vec::new() |
1046 | | )); |
1047 | | assert!(is_non_progressing( |
1048 | | &non_progressing, |
1049 | | &HashMap::new(), |
1050 | | &mut Vec::new() |
1051 | | )); |
1052 | | } |
1053 | | } |
1054 | | |
1055 | | #[test] |
1056 | | fn progressing_range() { |
1057 | | let progressing = ParserExpr::Range("A".into(), "Z".into()); |
1058 | | let failing_is_progressing = ParserExpr::Range("Z".into(), "A".into()); |
1059 | | |
1060 | | assert!(!is_non_progressing( |
1061 | | &progressing, |
1062 | | &HashMap::new(), |
1063 | | &mut Vec::new() |
1064 | | )); |
1065 | | assert!(!is_non_progressing( |
1066 | | &failing_is_progressing, |
1067 | | &HashMap::new(), |
1068 | | &mut Vec::new() |
1069 | | )); |
1070 | | } |
1071 | | |
1072 | | #[test] |
1073 | | fn progressing_choice() { |
1074 | | let left_progressing_node = Box::new(ParserNode { |
1075 | | expr: ParserExpr::Str("i'm make progress".into()), |
1076 | | span: Span::new(" ", 0, 1).unwrap(), |
1077 | | }); |
1078 | | assert!(!is_non_progressing( |
1079 | | &left_progressing_node.clone().expr, |
1080 | | &HashMap::new(), |
1081 | | &mut Vec::new() |
1082 | | )); |
1083 | | |
1084 | | let right_progressing_node = Box::new(ParserNode { |
1085 | | expr: ParserExpr::Ident("DROP".into()), |
1086 | | span: Span::new("DROP", 0, 3).unwrap(), |
1087 | | }); |
1088 | | |
1089 | | assert!(!is_non_progressing( |
1090 | | &ParserExpr::Choice(left_progressing_node, right_progressing_node), |
1091 | | &HashMap::new(), |
1092 | | &mut Vec::new() |
1093 | | )) |
1094 | | } |
1095 | | |
1096 | | #[test] |
1097 | | fn non_progressing_choices() { |
1098 | | let left_progressing_node = Box::new(ParserNode { |
1099 | | expr: ParserExpr::Str("i'm make progress".into()), |
1100 | | span: Span::new(" ", 0, 1).unwrap(), |
1101 | | }); |
1102 | | |
1103 | | assert!(!is_non_progressing( |
1104 | | &left_progressing_node.clone().expr, |
1105 | | &HashMap::new(), |
1106 | | &mut Vec::new() |
1107 | | )); |
1108 | | |
1109 | | let left_non_progressing_node = Box::new(ParserNode { |
1110 | | expr: ParserExpr::Str("".into()), |
1111 | | span: Span::new(" ", 0, 1).unwrap(), |
1112 | | }); |
1113 | | |
1114 | | assert!(is_non_progressing( |
1115 | | &left_non_progressing_node.clone().expr, |
1116 | | &HashMap::new(), |
1117 | | &mut Vec::new() |
1118 | | )); |
1119 | | |
1120 | | let right_progressing_node = Box::new(ParserNode { |
1121 | | expr: ParserExpr::Ident("DROP".into()), |
1122 | | span: Span::new("DROP", 0, 3).unwrap(), |
1123 | | }); |
1124 | | |
1125 | | assert!(!is_non_progressing( |
1126 | | &right_progressing_node.clone().expr, |
1127 | | &HashMap::new(), |
1128 | | &mut Vec::new() |
1129 | | )); |
1130 | | |
1131 | | let right_non_progressing_node = Box::new(ParserNode { |
1132 | | expr: ParserExpr::Opt(Box::new(ParserNode { |
1133 | | expr: ParserExpr::Str(" ".into()), |
1134 | | span: Span::new(" ", 0, 1).unwrap(), |
1135 | | })), |
1136 | | span: Span::new(" ", 0, 1).unwrap(), |
1137 | | }); |
1138 | | |
1139 | | assert!(is_non_progressing( |
1140 | | &right_non_progressing_node.clone().expr, |
1141 | | &HashMap::new(), |
1142 | | &mut Vec::new() |
1143 | | )); |
1144 | | |
1145 | | assert!(is_non_progressing( |
1146 | | &ParserExpr::Choice(left_non_progressing_node.clone(), right_progressing_node), |
1147 | | &HashMap::new(), |
1148 | | &mut Vec::new() |
1149 | | )); |
1150 | | assert!(is_non_progressing( |
1151 | | &ParserExpr::Choice(left_progressing_node, right_non_progressing_node.clone()), |
1152 | | &HashMap::new(), |
1153 | | &mut Vec::new() |
1154 | | )); |
1155 | | assert!(is_non_progressing( |
1156 | | &ParserExpr::Choice(left_non_progressing_node, right_non_progressing_node), |
1157 | | &HashMap::new(), |
1158 | | &mut Vec::new() |
1159 | | )) |
1160 | | } |
1161 | | |
1162 | | #[test] |
1163 | | fn non_progressing_seq() { |
1164 | | let left_non_progressing_node = Box::new(ParserNode { |
1165 | | expr: ParserExpr::Str("".into()), |
1166 | | span: Span::new(" ", 0, 1).unwrap(), |
1167 | | }); |
1168 | | |
1169 | | let right_non_progressing_node = Box::new(ParserNode { |
1170 | | expr: ParserExpr::Opt(Box::new(ParserNode { |
1171 | | expr: ParserExpr::Str(" ".into()), |
1172 | | span: Span::new(" ", 0, 1).unwrap(), |
1173 | | })), |
1174 | | span: Span::new(" ", 0, 1).unwrap(), |
1175 | | }); |
1176 | | |
1177 | | assert!(is_non_progressing( |
1178 | | &ParserExpr::Seq(left_non_progressing_node, right_non_progressing_node), |
1179 | | &HashMap::new(), |
1180 | | &mut Vec::new() |
1181 | | )) |
1182 | | } |
1183 | | |
1184 | | #[test] |
1185 | | fn progressing_seqs() { |
1186 | | let left_progressing_node = Box::new(ParserNode { |
1187 | | expr: ParserExpr::Str("i'm make progress".into()), |
1188 | | span: Span::new(" ", 0, 1).unwrap(), |
1189 | | }); |
1190 | | |
1191 | | assert!(!is_non_progressing( |
1192 | | &left_progressing_node.clone().expr, |
1193 | | &HashMap::new(), |
1194 | | &mut Vec::new() |
1195 | | )); |
1196 | | |
1197 | | let left_non_progressing_node = Box::new(ParserNode { |
1198 | | expr: ParserExpr::Str("".into()), |
1199 | | span: Span::new(" ", 0, 1).unwrap(), |
1200 | | }); |
1201 | | |
1202 | | assert!(is_non_progressing( |
1203 | | &left_non_progressing_node.clone().expr, |
1204 | | &HashMap::new(), |
1205 | | &mut Vec::new() |
1206 | | )); |
1207 | | |
1208 | | let right_progressing_node = Box::new(ParserNode { |
1209 | | expr: ParserExpr::Ident("DROP".into()), |
1210 | | span: Span::new("DROP", 0, 3).unwrap(), |
1211 | | }); |
1212 | | |
1213 | | assert!(!is_non_progressing( |
1214 | | &right_progressing_node.clone().expr, |
1215 | | &HashMap::new(), |
1216 | | &mut Vec::new() |
1217 | | )); |
1218 | | |
1219 | | let right_non_progressing_node = Box::new(ParserNode { |
1220 | | expr: ParserExpr::Opt(Box::new(ParserNode { |
1221 | | expr: ParserExpr::Str(" ".into()), |
1222 | | span: Span::new(" ", 0, 1).unwrap(), |
1223 | | })), |
1224 | | span: Span::new(" ", 0, 1).unwrap(), |
1225 | | }); |
1226 | | |
1227 | | assert!(is_non_progressing( |
1228 | | &right_non_progressing_node.clone().expr, |
1229 | | &HashMap::new(), |
1230 | | &mut Vec::new() |
1231 | | )); |
1232 | | |
1233 | | assert!(!is_non_progressing( |
1234 | | &ParserExpr::Seq(left_non_progressing_node, right_progressing_node.clone()), |
1235 | | &HashMap::new(), |
1236 | | &mut Vec::new() |
1237 | | )); |
1238 | | assert!(!is_non_progressing( |
1239 | | &ParserExpr::Seq(left_progressing_node.clone(), right_non_progressing_node), |
1240 | | &HashMap::new(), |
1241 | | &mut Vec::new() |
1242 | | )); |
1243 | | assert!(!is_non_progressing( |
1244 | | &ParserExpr::Seq(left_progressing_node, right_progressing_node), |
1245 | | &HashMap::new(), |
1246 | | &mut Vec::new() |
1247 | | )) |
1248 | | } |
1249 | | |
1250 | | #[test] |
1251 | | fn progressing_stack_operations() { |
1252 | | assert!(!is_non_progressing( |
1253 | | &ParserExpr::Ident("DROP".into()), |
1254 | | &HashMap::new(), |
1255 | | &mut Vec::new() |
1256 | | )); |
1257 | | assert!(!is_non_progressing( |
1258 | | &ParserExpr::Ident("PEEK".into()), |
1259 | | &HashMap::new(), |
1260 | | &mut Vec::new() |
1261 | | )); |
1262 | | assert!(!is_non_progressing( |
1263 | | &ParserExpr::Ident("POP".into()), |
1264 | | &HashMap::new(), |
1265 | | &mut Vec::new() |
1266 | | )) |
1267 | | } |
1268 | | |
1269 | | #[test] |
1270 | | fn non_failing_string() { |
1271 | | let insens = ParserExpr::Insens("".into()); |
1272 | | let string = ParserExpr::Str("".into()); |
1273 | | |
1274 | | assert!(is_non_failing(&insens, &HashMap::new(), &mut Vec::new())); |
1275 | | |
1276 | | assert!(is_non_failing(&string, &HashMap::new(), &mut Vec::new())) |
1277 | | } |
1278 | | |
1279 | | #[test] |
1280 | | fn failing_string() { |
1281 | | assert!(!is_non_failing( |
1282 | | &ParserExpr::Insens("i may fail!".into()), |
1283 | | &HashMap::new(), |
1284 | | &mut Vec::new() |
1285 | | )); |
1286 | | assert!(!is_non_failing( |
1287 | | &ParserExpr::Str("failure is not fatal".into()), |
1288 | | &HashMap::new(), |
1289 | | &mut Vec::new() |
1290 | | )) |
1291 | | } |
1292 | | |
1293 | | #[test] |
1294 | | fn failing_stack_operations() { |
1295 | | assert!(!is_non_failing( |
1296 | | &ParserExpr::Ident("DROP".into()), |
1297 | | &HashMap::new(), |
1298 | | &mut Vec::new() |
1299 | | )); |
1300 | | assert!(!is_non_failing( |
1301 | | &ParserExpr::Ident("POP".into()), |
1302 | | &HashMap::new(), |
1303 | | &mut Vec::new() |
1304 | | )); |
1305 | | assert!(!is_non_failing( |
1306 | | &ParserExpr::Ident("PEEK".into()), |
1307 | | &HashMap::new(), |
1308 | | &mut Vec::new() |
1309 | | )) |
1310 | | } |
1311 | | |
1312 | | #[test] |
1313 | | fn non_failing_zero_length_repetitions() { |
1314 | | let failing = Box::new(ParserNode { |
1315 | | expr: ParserExpr::Range("A".into(), "B".into()), |
1316 | | span: Span::new(" ", 0, 1).unwrap(), |
1317 | | }); |
1318 | | assert!(!is_non_failing( |
1319 | | &failing.clone().expr, |
1320 | | &HashMap::new(), |
1321 | | &mut Vec::new() |
1322 | | )); |
1323 | | assert!(is_non_failing( |
1324 | | &ParserExpr::Opt(failing.clone()), |
1325 | | &HashMap::new(), |
1326 | | &mut Vec::new() |
1327 | | )); |
1328 | | assert!(is_non_failing( |
1329 | | &ParserExpr::Rep(failing.clone()), |
1330 | | &HashMap::new(), |
1331 | | &mut Vec::new() |
1332 | | )); |
1333 | | assert!(is_non_failing( |
1334 | | &ParserExpr::RepExact(failing.clone(), 0), |
1335 | | &HashMap::new(), |
1336 | | &mut Vec::new() |
1337 | | )); |
1338 | | assert!(is_non_failing( |
1339 | | &ParserExpr::RepMin(failing.clone(), 0), |
1340 | | &HashMap::new(), |
1341 | | &mut Vec::new() |
1342 | | )); |
1343 | | assert!(is_non_failing( |
1344 | | &ParserExpr::RepMax(failing.clone(), 0), |
1345 | | &HashMap::new(), |
1346 | | &mut Vec::new() |
1347 | | )); |
1348 | | assert!(is_non_failing( |
1349 | | &ParserExpr::RepMax(failing.clone(), 22), |
1350 | | &HashMap::new(), |
1351 | | &mut Vec::new() |
1352 | | )); |
1353 | | assert!(is_non_failing( |
1354 | | &ParserExpr::RepMinMax(failing.clone(), 0, 73), |
1355 | | &HashMap::new(), |
1356 | | &mut Vec::new() |
1357 | | )); |
1358 | | } |
1359 | | |
1360 | | #[test] |
1361 | | fn non_failing_non_zero_repetitions_with_non_failing_expr() { |
1362 | | let non_failing = Box::new(ParserNode { |
1363 | | expr: ParserExpr::Opt(Box::new(ParserNode { |
1364 | | expr: ParserExpr::Range("A".into(), "B".into()), |
1365 | | span: Span::new(" ", 0, 1).unwrap(), |
1366 | | })), |
1367 | | span: Span::new(" ", 0, 1).unwrap(), |
1368 | | }); |
1369 | | assert!(is_non_failing( |
1370 | | &non_failing.clone().expr, |
1371 | | &HashMap::new(), |
1372 | | &mut Vec::new() |
1373 | | )); |
1374 | | assert!(is_non_failing( |
1375 | | &ParserExpr::RepOnce(non_failing.clone()), |
1376 | | &HashMap::new(), |
1377 | | &mut Vec::new() |
1378 | | )); |
1379 | | assert!(is_non_failing( |
1380 | | &ParserExpr::RepExact(non_failing.clone(), 1), |
1381 | | &HashMap::new(), |
1382 | | &mut Vec::new() |
1383 | | )); |
1384 | | assert!(is_non_failing( |
1385 | | &ParserExpr::RepMin(non_failing.clone(), 6), |
1386 | | &HashMap::new(), |
1387 | | &mut Vec::new() |
1388 | | )); |
1389 | | assert!(is_non_failing( |
1390 | | &ParserExpr::RepMinMax(non_failing.clone(), 32, 73), |
1391 | | &HashMap::new(), |
1392 | | &mut Vec::new() |
1393 | | )); |
1394 | | } |
1395 | | |
1396 | | #[test] |
1397 | | #[cfg(feature = "grammar-extras")] |
1398 | | fn failing_non_zero_repetitions() { |
1399 | | let failing = Box::new(ParserNode { |
1400 | | expr: ParserExpr::NodeTag( |
1401 | | Box::new(ParserNode { |
1402 | | expr: ParserExpr::Range("A".into(), "B".into()), |
1403 | | span: Span::new(" ", 0, 1).unwrap(), |
1404 | | }), |
1405 | | "Tag".into(), |
1406 | | ), |
1407 | | span: Span::new(" ", 0, 1).unwrap(), |
1408 | | }); |
1409 | | assert!(!is_non_failing( |
1410 | | &failing.clone().expr, |
1411 | | &HashMap::new(), |
1412 | | &mut Vec::new() |
1413 | | )); |
1414 | | assert!(!is_non_failing( |
1415 | | &ParserExpr::RepOnce(failing.clone()), |
1416 | | &HashMap::new(), |
1417 | | &mut Vec::new() |
1418 | | )); |
1419 | | assert!(!is_non_failing( |
1420 | | &ParserExpr::RepExact(failing.clone(), 3), |
1421 | | &HashMap::new(), |
1422 | | &mut Vec::new() |
1423 | | )); |
1424 | | assert!(!is_non_failing( |
1425 | | &ParserExpr::RepMin(failing.clone(), 14), |
1426 | | &HashMap::new(), |
1427 | | &mut Vec::new() |
1428 | | )); |
1429 | | assert!(!is_non_failing( |
1430 | | &ParserExpr::RepMinMax(failing.clone(), 47, 73), |
1431 | | &HashMap::new(), |
1432 | | &mut Vec::new() |
1433 | | )); |
1434 | | } |
1435 | | |
1436 | | #[test] |
1437 | | fn failing_choice() { |
1438 | | let left_failing_node = Box::new(ParserNode { |
1439 | | expr: ParserExpr::Str("i'm a failure".into()), |
1440 | | span: Span::new(" ", 0, 1).unwrap(), |
1441 | | }); |
1442 | | assert!(!is_non_failing( |
1443 | | &left_failing_node.clone().expr, |
1444 | | &HashMap::new(), |
1445 | | &mut Vec::new() |
1446 | | )); |
1447 | | |
1448 | | let right_failing_node = Box::new(ParserNode { |
1449 | | expr: ParserExpr::Ident("DROP".into()), |
1450 | | span: Span::new("DROP", 0, 3).unwrap(), |
1451 | | }); |
1452 | | |
1453 | | assert!(!is_non_failing( |
1454 | | &ParserExpr::Choice(left_failing_node, right_failing_node), |
1455 | | &HashMap::new(), |
1456 | | &mut Vec::new() |
1457 | | )) |
1458 | | } |
1459 | | |
1460 | | #[test] |
1461 | | fn non_failing_choices() { |
1462 | | let left_failing_node = Box::new(ParserNode { |
1463 | | expr: ParserExpr::Str("i'm a failure".into()), |
1464 | | span: Span::new(" ", 0, 1).unwrap(), |
1465 | | }); |
1466 | | |
1467 | | assert!(!is_non_failing( |
1468 | | &left_failing_node.clone().expr, |
1469 | | &HashMap::new(), |
1470 | | &mut Vec::new() |
1471 | | )); |
1472 | | |
1473 | | let left_non_failing_node = Box::new(ParserNode { |
1474 | | expr: ParserExpr::Str("".into()), |
1475 | | span: Span::new(" ", 0, 1).unwrap(), |
1476 | | }); |
1477 | | |
1478 | | assert!(is_non_failing( |
1479 | | &left_non_failing_node.clone().expr, |
1480 | | &HashMap::new(), |
1481 | | &mut Vec::new() |
1482 | | )); |
1483 | | |
1484 | | let right_failing_node = Box::new(ParserNode { |
1485 | | expr: ParserExpr::Ident("DROP".into()), |
1486 | | span: Span::new("DROP", 0, 3).unwrap(), |
1487 | | }); |
1488 | | |
1489 | | assert!(!is_non_failing( |
1490 | | &right_failing_node.clone().expr, |
1491 | | &HashMap::new(), |
1492 | | &mut Vec::new() |
1493 | | )); |
1494 | | |
1495 | | let right_non_failing_node = Box::new(ParserNode { |
1496 | | expr: ParserExpr::Opt(Box::new(ParserNode { |
1497 | | expr: ParserExpr::Str(" ".into()), |
1498 | | span: Span::new(" ", 0, 1).unwrap(), |
1499 | | })), |
1500 | | span: Span::new(" ", 0, 1).unwrap(), |
1501 | | }); |
1502 | | |
1503 | | assert!(is_non_failing( |
1504 | | &right_non_failing_node.clone().expr, |
1505 | | &HashMap::new(), |
1506 | | &mut Vec::new() |
1507 | | )); |
1508 | | |
1509 | | assert!(is_non_failing( |
1510 | | &ParserExpr::Choice(left_non_failing_node.clone(), right_failing_node), |
1511 | | &HashMap::new(), |
1512 | | &mut Vec::new() |
1513 | | )); |
1514 | | assert!(is_non_failing( |
1515 | | &ParserExpr::Choice(left_failing_node, right_non_failing_node.clone()), |
1516 | | &HashMap::new(), |
1517 | | &mut Vec::new() |
1518 | | )); |
1519 | | assert!(is_non_failing( |
1520 | | &ParserExpr::Choice(left_non_failing_node, right_non_failing_node), |
1521 | | &HashMap::new(), |
1522 | | &mut Vec::new() |
1523 | | )) |
1524 | | } |
1525 | | |
1526 | | #[test] |
1527 | | fn non_failing_seq() { |
1528 | | let left_non_failing_node = Box::new(ParserNode { |
1529 | | expr: ParserExpr::Str("".into()), |
1530 | | span: Span::new(" ", 0, 1).unwrap(), |
1531 | | }); |
1532 | | |
1533 | | let right_non_failing_node = Box::new(ParserNode { |
1534 | | expr: ParserExpr::Opt(Box::new(ParserNode { |
1535 | | expr: ParserExpr::Str(" ".into()), |
1536 | | span: Span::new(" ", 0, 1).unwrap(), |
1537 | | })), |
1538 | | span: Span::new(" ", 0, 1).unwrap(), |
1539 | | }); |
1540 | | |
1541 | | assert!(is_non_failing( |
1542 | | &ParserExpr::Seq(left_non_failing_node, right_non_failing_node), |
1543 | | &HashMap::new(), |
1544 | | &mut Vec::new() |
1545 | | )) |
1546 | | } |
1547 | | |
1548 | | #[test] |
1549 | | fn failing_seqs() { |
1550 | | let left_failing_node = Box::new(ParserNode { |
1551 | | expr: ParserExpr::Str("i'm a failure".into()), |
1552 | | span: Span::new(" ", 0, 1).unwrap(), |
1553 | | }); |
1554 | | |
1555 | | assert!(!is_non_failing( |
1556 | | &left_failing_node.clone().expr, |
1557 | | &HashMap::new(), |
1558 | | &mut Vec::new() |
1559 | | )); |
1560 | | |
1561 | | let left_non_failing_node = Box::new(ParserNode { |
1562 | | expr: ParserExpr::Str("".into()), |
1563 | | span: Span::new(" ", 0, 1).unwrap(), |
1564 | | }); |
1565 | | |
1566 | | assert!(is_non_failing( |
1567 | | &left_non_failing_node.clone().expr, |
1568 | | &HashMap::new(), |
1569 | | &mut Vec::new() |
1570 | | )); |
1571 | | |
1572 | | let right_failing_node = Box::new(ParserNode { |
1573 | | expr: ParserExpr::Ident("DROP".into()), |
1574 | | span: Span::new("DROP", 0, 3).unwrap(), |
1575 | | }); |
1576 | | |
1577 | | assert!(!is_non_failing( |
1578 | | &right_failing_node.clone().expr, |
1579 | | &HashMap::new(), |
1580 | | &mut Vec::new() |
1581 | | )); |
1582 | | |
1583 | | let right_non_failing_node = Box::new(ParserNode { |
1584 | | expr: ParserExpr::Opt(Box::new(ParserNode { |
1585 | | expr: ParserExpr::Str(" ".into()), |
1586 | | span: Span::new(" ", 0, 1).unwrap(), |
1587 | | })), |
1588 | | span: Span::new(" ", 0, 1).unwrap(), |
1589 | | }); |
1590 | | |
1591 | | assert!(is_non_failing( |
1592 | | &right_non_failing_node.clone().expr, |
1593 | | &HashMap::new(), |
1594 | | &mut Vec::new() |
1595 | | )); |
1596 | | |
1597 | | assert!(!is_non_failing( |
1598 | | &ParserExpr::Seq(left_non_failing_node, right_failing_node.clone()), |
1599 | | &HashMap::new(), |
1600 | | &mut Vec::new() |
1601 | | )); |
1602 | | assert!(!is_non_failing( |
1603 | | &ParserExpr::Seq(left_failing_node.clone(), right_non_failing_node), |
1604 | | &HashMap::new(), |
1605 | | &mut Vec::new() |
1606 | | )); |
1607 | | assert!(!is_non_failing( |
1608 | | &ParserExpr::Seq(left_failing_node, right_failing_node), |
1609 | | &HashMap::new(), |
1610 | | &mut Vec::new() |
1611 | | )) |
1612 | | } |
1613 | | |
1614 | | #[test] |
1615 | | fn failing_range() { |
1616 | | let failing = ParserExpr::Range("A".into(), "Z".into()); |
1617 | | let always_failing = ParserExpr::Range("Z".into(), "A".into()); |
1618 | | |
1619 | | assert!(!is_non_failing(&failing, &HashMap::new(), &mut Vec::new())); |
1620 | | assert!(!is_non_failing( |
1621 | | &always_failing, |
1622 | | &HashMap::new(), |
1623 | | &mut Vec::new() |
1624 | | )); |
1625 | | } |
1626 | | |
1627 | | #[test] |
1628 | | fn _push_node_tag_pos_pred_forwarding_is_non_failing() { |
1629 | | let failing_node = Box::new(ParserNode { |
1630 | | expr: ParserExpr::Str("i'm a failure".into()), |
1631 | | span: Span::new(" ", 0, 1).unwrap(), |
1632 | | }); |
1633 | | assert!(!is_non_failing( |
1634 | | &failing_node.clone().expr, |
1635 | | &HashMap::new(), |
1636 | | &mut Vec::new() |
1637 | | )); |
1638 | | let non_failing_node = Box::new(ParserNode { |
1639 | | expr: ParserExpr::Str("".into()), |
1640 | | span: Span::new(" ", 0, 1).unwrap(), |
1641 | | }); |
1642 | | assert!(is_non_failing( |
1643 | | &non_failing_node.clone().expr, |
1644 | | &HashMap::new(), |
1645 | | &mut Vec::new() |
1646 | | )); |
1647 | | |
1648 | | #[cfg(feature = "grammar-extras")] |
1649 | | { |
1650 | | assert!(!is_non_failing( |
1651 | | &ParserExpr::NodeTag(failing_node.clone(), "TAG".into()), |
1652 | | &HashMap::new(), |
1653 | | &mut Vec::new() |
1654 | | )); |
1655 | | assert!(is_non_failing( |
1656 | | &ParserExpr::NodeTag(non_failing_node.clone(), "TAG".into()), |
1657 | | &HashMap::new(), |
1658 | | &mut Vec::new() |
1659 | | )); |
1660 | | } |
1661 | | |
1662 | | assert!(!is_non_failing( |
1663 | | &ParserExpr::Push(failing_node.clone()), |
1664 | | &HashMap::new(), |
1665 | | &mut Vec::new() |
1666 | | )); |
1667 | | assert!(is_non_failing( |
1668 | | &ParserExpr::Push(non_failing_node.clone()), |
1669 | | &HashMap::new(), |
1670 | | &mut Vec::new() |
1671 | | )); |
1672 | | |
1673 | | assert!(!is_non_failing( |
1674 | | &ParserExpr::PosPred(failing_node.clone()), |
1675 | | &HashMap::new(), |
1676 | | &mut Vec::new() |
1677 | | )); |
1678 | | assert!(is_non_failing( |
1679 | | &ParserExpr::PosPred(non_failing_node.clone()), |
1680 | | &HashMap::new(), |
1681 | | &mut Vec::new() |
1682 | | )); |
1683 | | } |
1684 | | |
1685 | | #[cfg(feature = "grammar-extras")] |
1686 | | #[test] |
1687 | | fn push_literal_is_non_failing() { |
1688 | | assert!(is_non_failing( |
1689 | | &ParserExpr::PushLiteral("a".to_string()), |
1690 | | &HashMap::new(), |
1691 | | &mut Vec::new() |
1692 | | )); |
1693 | | } |
1694 | | |
1695 | | #[test] |
1696 | | #[should_panic(expected = "grammar error |
1697 | | |
1698 | | --> 1:7 |
1699 | | | |
1700 | | 1 | a = { (\"\")* } |
1701 | | | ^---^ |
1702 | | | |
1703 | | = expression inside repetition cannot fail and will repeat infinitely")] |
1704 | | fn non_failing_repetition() { |
1705 | | let input = "a = { (\"\")* }"; |
1706 | | unwrap_or_report(consume_rules( |
1707 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1708 | | )); |
1709 | | } |
1710 | | |
1711 | | #[test] |
1712 | | #[should_panic(expected = "grammar error |
1713 | | |
1714 | | --> 1:18 |
1715 | | | |
1716 | | 1 | a = { \"\" } b = { a* } |
1717 | | | ^^ |
1718 | | | |
1719 | | = expression inside repetition cannot fail and will repeat infinitely")] |
1720 | | fn indirect_non_failing_repetition() { |
1721 | | let input = "a = { \"\" } b = { a* }"; |
1722 | | unwrap_or_report(consume_rules( |
1723 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1724 | | )); |
1725 | | } |
1726 | | |
1727 | | #[test] |
1728 | | #[should_panic(expected = "grammar error |
1729 | | |
1730 | | --> 1:20 |
1731 | | | |
1732 | | 1 | a = { \"a\" ~ (\"b\" ~ (\"\")*) } |
1733 | | | ^---^ |
1734 | | | |
1735 | | = expression inside repetition cannot fail and will repeat infinitely")] |
1736 | | fn deep_non_failing_repetition() { |
1737 | | let input = "a = { \"a\" ~ (\"b\" ~ (\"\")*) }"; |
1738 | | unwrap_or_report(consume_rules( |
1739 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1740 | | )); |
1741 | | } |
1742 | | |
1743 | | #[test] |
1744 | | #[should_panic(expected = "grammar error |
1745 | | |
1746 | | --> 1:7 |
1747 | | | |
1748 | | 1 | a = { (\"\" ~ &\"a\" ~ !\"a\" ~ (SOI | EOI))* } |
1749 | | | ^-------------------------------^ |
1750 | | | |
1751 | | = expression inside repetition is non-progressing and will repeat infinitely")] |
1752 | | fn non_progressing_repetition() { |
1753 | | let input = "a = { (\"\" ~ &\"a\" ~ !\"a\" ~ (SOI | EOI))* }"; |
1754 | | unwrap_or_report(consume_rules( |
1755 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1756 | | )); |
1757 | | } |
1758 | | |
1759 | | #[test] |
1760 | | #[should_panic(expected = "grammar error |
1761 | | |
1762 | | --> 1:20 |
1763 | | | |
1764 | | 1 | a = { !\"a\" } b = { a* } |
1765 | | | ^^ |
1766 | | | |
1767 | | = expression inside repetition is non-progressing and will repeat infinitely")] |
1768 | | fn indirect_non_progressing_repetition() { |
1769 | | let input = "a = { !\"a\" } b = { a* }"; |
1770 | | unwrap_or_report(consume_rules( |
1771 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1772 | | )); |
1773 | | } |
1774 | | |
1775 | | #[test] |
1776 | | #[should_panic(expected = "grammar error |
1777 | | |
1778 | | --> 1:7 |
1779 | | | |
1780 | | 1 | a = { a } |
1781 | | | ^ |
1782 | | | |
1783 | | = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] |
1784 | | fn simple_left_recursion() { |
1785 | | let input = "a = { a }"; |
1786 | | unwrap_or_report(consume_rules( |
1787 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1788 | | )); |
1789 | | } |
1790 | | |
1791 | | #[test] |
1792 | | #[should_panic(expected = "grammar error |
1793 | | |
1794 | | --> 1:7 |
1795 | | | |
1796 | | 1 | a = { b } b = { a } |
1797 | | | ^ |
1798 | | | |
1799 | | = rule b is left-recursive (b -> a -> b); pest::pratt_parser might be useful in this case |
1800 | | |
1801 | | --> 1:17 |
1802 | | | |
1803 | | 1 | a = { b } b = { a } |
1804 | | | ^ |
1805 | | | |
1806 | | = rule a is left-recursive (a -> b -> a); pest::pratt_parser might be useful in this case")] |
1807 | | fn indirect_left_recursion() { |
1808 | | let input = "a = { b } b = { a }"; |
1809 | | unwrap_or_report(consume_rules( |
1810 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1811 | | )); |
1812 | | } |
1813 | | |
1814 | | #[test] |
1815 | | #[should_panic(expected = "grammar error |
1816 | | |
1817 | | --> 1:39 |
1818 | | | |
1819 | | 1 | a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"\") ~ a } |
1820 | | | ^ |
1821 | | | |
1822 | | = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] |
1823 | | fn non_failing_left_recursion() { |
1824 | | let input = "a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"\") ~ a }"; |
1825 | | unwrap_or_report(consume_rules( |
1826 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1827 | | )); |
1828 | | } |
1829 | | |
1830 | | #[test] |
1831 | | #[should_panic(expected = "grammar error |
1832 | | |
1833 | | --> 1:13 |
1834 | | | |
1835 | | 1 | a = { \"a\" | a } |
1836 | | | ^ |
1837 | | | |
1838 | | = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] |
1839 | | fn non_primary_choice_left_recursion() { |
1840 | | let input = "a = { \"a\" | a }"; |
1841 | | unwrap_or_report(consume_rules( |
1842 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1843 | | )); |
1844 | | } |
1845 | | |
1846 | | #[test] |
1847 | | #[should_panic(expected = "grammar error |
1848 | | |
1849 | | --> 1:14 |
1850 | | | |
1851 | | 1 | a = { !\"a\" ~ a } |
1852 | | | ^ |
1853 | | | |
1854 | | = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] |
1855 | | fn non_progressing_left_recursion() { |
1856 | | let input = "a = { !\"a\" ~ a }"; |
1857 | | unwrap_or_report(consume_rules( |
1858 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1859 | | )); |
1860 | | } |
1861 | | |
1862 | | #[test] |
1863 | | #[should_panic(expected = "grammar error |
1864 | | |
1865 | | --> 1:7 |
1866 | | | |
1867 | | 1 | a = { \"a\"* | \"a\" | \"b\" } |
1868 | | | ^--^ |
1869 | | | |
1870 | | = expression cannot fail; following choices cannot be reached")] |
1871 | | fn lhs_non_failing_choice() { |
1872 | | let input = "a = { \"a\"* | \"a\" | \"b\" }"; |
1873 | | unwrap_or_report(consume_rules( |
1874 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1875 | | )); |
1876 | | } |
1877 | | |
1878 | | #[test] |
1879 | | #[should_panic(expected = "grammar error |
1880 | | |
1881 | | --> 1:13 |
1882 | | | |
1883 | | 1 | a = { \"a\" | \"a\"* | \"b\" } |
1884 | | | ^--^ |
1885 | | | |
1886 | | = expression cannot fail; following choices cannot be reached")] |
1887 | | fn lhs_non_failing_choice_middle() { |
1888 | | let input = "a = { \"a\" | \"a\"* | \"b\" }"; |
1889 | | unwrap_or_report(consume_rules( |
1890 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1891 | | )); |
1892 | | } |
1893 | | |
1894 | | #[test] |
1895 | | #[should_panic(expected = "grammar error |
1896 | | |
1897 | | --> 1:7 |
1898 | | | |
1899 | | 1 | a = { b | \"a\" } b = { \"b\"* | \"c\" } |
1900 | | | ^ |
1901 | | | |
1902 | | = expression cannot fail; following choices cannot be reached |
1903 | | |
1904 | | --> 1:23 |
1905 | | | |
1906 | | 1 | a = { b | \"a\" } b = { \"b\"* | \"c\" } |
1907 | | | ^--^ |
1908 | | | |
1909 | | = expression cannot fail; following choices cannot be reached")] |
1910 | | fn lhs_non_failing_nested_choices() { |
1911 | | let input = "a = { b | \"a\" } b = { \"b\"* | \"c\" }"; |
1912 | | unwrap_or_report(consume_rules( |
1913 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1914 | | )); |
1915 | | } |
1916 | | |
1917 | | #[test] |
1918 | | fn skip_can_be_defined() { |
1919 | | let input = "skip = { \"\" }"; |
1920 | | unwrap_or_report(consume_rules( |
1921 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1922 | | )); |
1923 | | } |
1924 | | |
1925 | | #[test] |
1926 | | #[should_panic(expected = "grammar error |
1927 | | |
1928 | | --> 1:7 |
1929 | | | |
1930 | | 1 | a = { #b = b } b = _{ ASCII_DIGIT+ } |
1931 | | | ^----^ |
1932 | | | |
1933 | | = tags on silent rules will not appear in the output")] |
1934 | | #[cfg(feature = "grammar-extras")] |
1935 | | fn tag_on_silent_rule() { |
1936 | | let input = "a = { #b = b } b = _{ ASCII_DIGIT+ }"; |
1937 | | unwrap_or_report(consume_rules( |
1938 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1939 | | )); |
1940 | | } |
1941 | | |
1942 | | #[test] |
1943 | | #[should_panic(expected = "grammar error |
1944 | | |
1945 | | --> 1:7 |
1946 | | | |
1947 | | 1 | a = { #b = ASCII_DIGIT+ } |
1948 | | | ^---------------^ |
1949 | | | |
1950 | | = tags on built-in rules will not appear in the output")] |
1951 | | #[cfg(feature = "grammar-extras")] |
1952 | | fn tag_on_builtin_rule() { |
1953 | | let input = "a = { #b = ASCII_DIGIT+ }"; |
1954 | | unwrap_or_report(consume_rules( |
1955 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1956 | | )); |
1957 | | } |
1958 | | |
1959 | | #[test] |
1960 | | #[cfg(feature = "grammar-extras")] |
1961 | | fn tag_on_normal_rule() { |
1962 | | let input = "a = { #b = b } b = { ASCII_DIGIT+ }"; |
1963 | | unwrap_or_report(consume_rules( |
1964 | | PestParser::parse(Rule::grammar_rules, input).unwrap(), |
1965 | | )); |
1966 | | } |
1967 | | } |