pest_meta-2.7.4/Cargo.toml0000644000000025270000000000100110330ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.61" name = "pest_meta" version = "2.7.4" authors = ["Dragoș Tiselice "] exclude = ["src/grammar.pest"] include = [ "Cargo.toml", "src/**/*", "src/grammar.rs", "_README.md", "LICENSE-*", ] description = "pest meta language parser and validator" homepage = "https://pest.rs/" documentation = "https://docs.rs/pest" readme = "_README.md" keywords = [ "pest", "parser", "meta", "optimizer", ] categories = ["parsing"] license = "MIT OR Apache-2.0" repository = "https://github.com/pest-parser/pest" [dependencies.once_cell] version = "1.8.0" [dependencies.pest] version = "2.7.4" [build-dependencies.cargo] version = "0.72.2" optional = true [build-dependencies.sha2] version = "0.10" default-features = false [features] default = [] grammar-extras = [] not-bootstrap-in-src = ["dep:cargo"] pest_meta-2.7.4/Cargo.toml.orig000064400000000000000000000015211046102023000145050ustar 00000000000000[package] name = "pest_meta" description = "pest meta language parser and validator" version = "2.7.4" edition = "2021" authors = ["Dragoș Tiselice "] homepage = "https://pest.rs/" repository = "https://github.com/pest-parser/pest" documentation = "https://docs.rs/pest" keywords = ["pest", "parser", "meta", "optimizer"] categories = ["parsing"] license = "MIT OR Apache-2.0" readme = "_README.md" exclude = ["src/grammar.pest"] include = ["Cargo.toml", "src/**/*", "src/grammar.rs", "_README.md", "LICENSE-*"] rust-version = "1.61" [dependencies] pest = { path = "../pest", version = "2.7.4" } once_cell = "1.8.0" [build-dependencies] sha2 = { version = "0.10", default-features = false } cargo = { version = "0.72.2", optional = true } [features] default = [] not-bootstrap-in-src = ["dep:cargo"] grammar-extras = []pest_meta-2.7.4/LICENSE-APACHE000064400000000000000000000251371046102023000135530ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. pest_meta-2.7.4/LICENSE-MIT000064400000000000000000000017771046102023000132670ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pest_meta-2.7.4/_README.md000064400000000000000000000175721046102023000132510ustar 00000000000000

# pest. The Elegant Parser [![Join the chat at https://gitter.im/pest-parser/pest](https://badges.gitter.im/dragostis/pest.svg)](https://gitter.im/pest-parser/pest?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Book](https://img.shields.io/badge/book-WIP-4d76ae.svg)](https://pest.rs/book) [![Docs](https://docs.rs/pest/badge.svg)](https://docs.rs/pest) [![pest Continuous Integration](https://github.com/pest-parser/pest/actions/workflows/ci.yml/badge.svg)](https://github.com/pest-parser/pest/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/pest-parser/pest/branch/master/graph/badge.svg)](https://codecov.io/gh/pest-parser/pest) Rustc Version 1.61.0+ [![Crates.io](https://img.shields.io/crates/d/pest.svg)](https://crates.io/crates/pest) [![Crates.io](https://img.shields.io/crates/v/pest.svg)](https://crates.io/crates/pest) pest is a general purpose parser written in Rust with a focus on accessibility, correctness, and performance. It uses parsing expression grammars (or [PEG]) as input, which are similar in spirit to regular expressions, but which offer the enhanced expressivity needed to parse complex languages. [PEG]: https://en.wikipedia.org/wiki/Parsing_expression_grammar ## Getting started The recommended way to start parsing with pest is to read the official [book]. Other helpful resources: * API reference on [docs.rs] * play with grammars and share them on our [fiddle] * find previous common questions answered or ask questions on [GitHub Discussions] * leave feedback, ask questions, or greet us on [Gitter] or [Discord] [book]: https://pest.rs/book [docs.rs]: https://docs.rs/pest [fiddle]: https://pest.rs/#editor [Gitter]: https://gitter.im/pest-parser/pest [Discord]: https://discord.gg/XEGACtWpT2 [GitHub Discussions]: https://github.com/pest-parser/pest/discussions ## Example The following is an example of a grammar for a list of alphanumeric identifiers where all identifiers don't start with a digit: ```rust alpha = { 'a'..'z' | 'A'..'Z' } digit = { '0'..'9' } ident = { !digit ~ (alpha | digit)+ } ident_list = _{ ident ~ (" " ~ ident)* } // ^ // ident_list rule is silent which means it produces no tokens ``` Grammars are saved in separate .pest files which are never mixed with procedural code. This results in an always up-to-date formalization of a language that is easy to read and maintain. ## Meaningful error reporting Based on the grammar definition, the parser also includes automatic error reporting. For the example above, the input `"123"` will result in: ``` thread 'main' panicked at ' --> 1:1 | 1 | 123 | ^--- | = unexpected digit', src/main.rs:12 ``` while `"ab *"` will result in: ``` thread 'main' panicked at ' --> 1:1 | 1 | ab * | ^--- | = expected ident', src/main.rs:12 ``` These error messages can be obtained from their default `Display` implementation, e.g. `panic!("{}", parser_result.unwrap_err())` or `println!("{}", e)`. ## Pairs API The grammar can be used to derive a `Parser` implementation automatically. Parsing returns an iterator of nested token pairs: ```rust extern crate pest; #[macro_use] extern crate pest_derive; use pest::Parser; #[derive(Parser)] #[grammar = "ident.pest"] struct IdentParser; fn main() {    let pairs = IdentParser::parse(Rule::ident_list, "a1 b2").unwrap_or_else(|e| panic!("{}", e)); // Because ident_list is silent, the iterator will contain idents for pair in pairs { // A pair is a combination of the rule which matched and a span of input println!("Rule: {:?}", pair.as_rule()); println!("Span: {:?}", pair.as_span()); println!("Text: {}", pair.as_str()); // A pair can be converted to an iterator of the tokens which make it up: for inner_pair in pair.into_inner() { match inner_pair.as_rule() { Rule::alpha => println!("Letter: {}", inner_pair.as_str()), Rule::digit => println!("Digit: {}", inner_pair.as_str()), _ => unreachable!() }; } } } ``` This produces the following output: ``` Rule: ident Span: Span { start: 0, end: 2 } Text: a1 Letter: a Digit: 1 Rule: ident Span: Span { start: 3, end: 5 } Text: b2 Letter: b Digit: 2 ``` ### Defining multiple parsers in a single file The current automatic `Parser` derivation will produce the `Rule` enum which would have name conflicts if one tried to define multiple such structs that automatically derive `Parser`. One possible way around it is to put each parser struct in a separate namespace: ```rust mod a { #[derive(Parser)] #[grammar = "a.pest"] pub struct ParserA; } mod b { #[derive(Parser)] #[grammar = "b.pest"] pub struct ParserB; } ``` ## Other features * Precedence climbing * Input handling * Custom errors * Runs on stable Rust ## Projects using pest You can find more projects and ecosystem tools in the [awesome-pest](https://github.com/pest-parser/awesome-pest) repo. * [pest_meta](https://github.com/pest-parser/pest/blob/master/meta/src/grammar.pest) (bootstrapped) * [AshPaper](https://github.com/shnewto/ashpaper) * [brain](https://github.com/brain-lang/brain) * [cicada](https://github.com/mitnk/cicada) * [comrak](https://github.com/kivikakk/comrak) * [elastic-rs](https://github.com/cch123/elastic-rs) * [graphql-parser](https://github.com/Keats/graphql-parser) * [handlebars-rust](https://github.com/sunng87/handlebars-rust) * [hexdino](https://github.com/Luz/hexdino) * [Huia](https://gitlab.com/jimsy/huia/) * [insta](https://github.com/mitsuhiko/insta) * [jql](https://github.com/yamafaktory/jql) * [json5-rs](https://github.com/callum-oakley/json5-rs) * [mt940](https://github.com/svenstaro/mt940-rs) * [Myoxine](https://github.com/d3bate/myoxine) * [py_literal](https://github.com/jturner314/py_literal) * [rouler](https://github.com/jarcane/rouler) * [RuSh](https://github.com/lwandrebeck/RuSh) * [rs_pbrt](https://github.com/wahn/rs_pbrt) * [stache](https://github.com/dgraham/stache) * [tera](https://github.com/Keats/tera) * [ui_gen](https://github.com/emoon/ui_gen) * [ukhasnet-parser](https://github.com/adamgreig/ukhasnet-parser) * [ZoKrates](https://github.com/ZoKrates/ZoKrates) * [Vector](https://github.com/timberio/vector) * [AutoCorrect](https://github.com/huacnlee/autocorrect) * [yaml-peg](https://github.com/aofdev/yaml-peg) * [qubit](https://github.com/abhimanyu003/qubit) * [caith](https://github.com/Geobert/caith) (a dice roller crate) * [Melody](https://github.com/yoav-lavi/melody) * [json5-nodes](https://github.com/jlyonsmith/json5-nodes) * [prisma](https://github.com/prisma/prisma) ## Minimum Supported Rust Version (MSRV) This library should always compile with default features on **Rust 1.61.0**. ## no_std support The `pest` and `pest_derive` crates can be built without the Rust standard library and target embedded environments. To do so, you need to disable their default features. In your `Cargo.toml`, you can specify it as follows: ```toml [dependencies] # ... pest = { version = "2", default-features = false } pest_derive = { version = "2", default-features = false } ``` If you want to build these crates in the pest repository's workspace, you can pass the `--no-default-features` flag to `cargo` and specify these crates using the `--package` (`-p`) flag. For example: ```bash $ cargo build --target thumbv7em-none-eabihf --no-default-features -p pest $ cargo bootstrap $ cargo build --target thumbv7em-none-eabihf --no-default-features -p pest_derive ``` ## Special thanks A special round of applause goes to prof. Marius Minea for his guidance and all pest contributors, some of which being none other than my friends. pest_meta-2.7.4/src/ast.rs000064400000000000000000000570301046102023000135500ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. //! Types for the pest's abstract syntax tree. /// A grammar rule #[derive(Clone, Debug, Eq, PartialEq)] pub struct Rule { /// The name of the rule pub name: String, /// The rule's type (silent, atomic, ...) pub ty: RuleType, /// The rule's expression pub expr: Expr, } /// All possible rule types #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RuleType { /// The normal rule type Normal, /// Silent rules are just like normal rules /// — when run, they function the same way — /// except they do not produce pairs or tokens. /// If a rule is silent, it will never appear in a parse result. /// (their syntax is `_{ ... }`) Silent, /// atomic rule prevent implicit whitespace: inside an atomic rule, /// the tilde ~ means "immediately followed by", /// and repetition operators (asterisk * and plus sign +) /// have no implicit separation. In addition, all other rules /// called from an atomic rule are also treated as atomic. /// In an atomic rule, interior matching rules are silent. /// (their syntax is `@{ ... }`) Atomic, /// Compound atomic rules are similar to atomic rules, /// but they produce inner tokens as normal. /// (their syntax is `${ ... }`) CompoundAtomic, /// Non-atomic rules cancel the effect of atomic rules. /// (their syntax is `!{ ... }`) NonAtomic, } /// All possible rule expressions /// /// # Warning: Semantic Versioning /// There may be non-breaking changes to the meta-grammar /// between minor versions. Those non-breaking changes, however, /// may translate into semver-breaking changes due to the additional variants /// propaged from the `Rule` enum. This is a known issue and will be fixed in the /// future (e.g. by increasing MSRV and non_exhaustive annotations). #[derive(Clone, Debug, Eq, PartialEq)] pub enum Expr { /// Matches an exact string, e.g. `"a"` Str(String), /// Matches an exact string, case insensitively (ASCII only), e.g. `^"a"` Insens(String), /// Matches one character in the range, e.g. `'a'..'z'` Range(String, String), /// Matches the rule with the given name, e.g. `a` Ident(String), /// Matches a custom part of the stack, e.g. `PEEK[..]` PeekSlice(i32, Option), /// Positive lookahead; matches expression without making progress, e.g. `&e` PosPred(Box), /// Negative lookahead; matches if expression doesn't match, without making progress, e.g. `!e` NegPred(Box), /// Matches a sequence of two expressions, e.g. `e1 ~ e2` Seq(Box, Box), /// Matches either of two expressions, e.g. `e1 | e2` Choice(Box, Box), /// Optionally matches an expression, e.g. `e?` Opt(Box), /// Matches an expression zero or more times, e.g. `e*` Rep(Box), /// Matches an expression one or more times, e.g. `e+` RepOnce(Box), /// Matches an expression an exact number of times, e.g. `e{n}` RepExact(Box, u32), /// Matches an expression at least a number of times, e.g. `e{n,}` RepMin(Box, u32), /// Matches an expression at most a number of times, e.g. `e{,n}` RepMax(Box, u32), /// Matches an expression a number of times within a range, e.g. `e{m, n}` RepMinMax(Box, u32, u32), /// Continues to match expressions until one of the strings in the `Vec` is found Skip(Vec), /// Matches an expression and pushes it to the stack, e.g. `push(e)` Push(Box), /// Matches an expression and assigns a label to it, e.g. #label = exp #[cfg(feature = "grammar-extras")] NodeTag(Box, String), } impl Expr { /// Returns the iterator that steps the expression from top to bottom. pub fn iter_top_down(&self) -> ExprTopDownIterator { ExprTopDownIterator::new(self) } /// Applies `f` to the expression and all its children (top to bottom). pub fn map_top_down(self, mut f: F) -> Expr where F: FnMut(Expr) -> Expr, { fn map_internal(expr: Expr, f: &mut F) -> Expr where F: FnMut(Expr) -> Expr, { let expr = f(expr); match expr { Expr::PosPred(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::PosPred(mapped) } Expr::NegPred(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::NegPred(mapped) } Expr::Seq(lhs, rhs) => { let mapped_lhs = Box::new(map_internal(*lhs, f)); let mapped_rhs = Box::new(map_internal(*rhs, f)); Expr::Seq(mapped_lhs, mapped_rhs) } Expr::Choice(lhs, rhs) => { let mapped_lhs = Box::new(map_internal(*lhs, f)); let mapped_rhs = Box::new(map_internal(*rhs, f)); Expr::Choice(mapped_lhs, mapped_rhs) } Expr::Rep(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::Rep(mapped) } Expr::RepOnce(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepOnce(mapped) } Expr::RepExact(expr, max) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepExact(mapped, max) } Expr::RepMin(expr, num) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepMin(mapped, num) } Expr::RepMax(expr, num) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepMax(mapped, num) } Expr::RepMinMax(expr, min, max) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepMinMax(mapped, min, max) } Expr::Opt(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::Opt(mapped) } Expr::Push(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::Push(mapped) } #[cfg(feature = "grammar-extras")] Expr::NodeTag(expr, tag) => { let mapped = Box::new(map_internal(*expr, f)); Expr::NodeTag(mapped, tag) } expr => expr, } } map_internal(self, &mut f) } /// Applies `f` to the expression and all its children (bottom up). pub fn map_bottom_up(self, mut f: F) -> Expr where F: FnMut(Expr) -> Expr, { fn map_internal(expr: Expr, f: &mut F) -> Expr where F: FnMut(Expr) -> Expr, { let mapped = match expr { Expr::PosPred(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::PosPred(mapped) } Expr::NegPred(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::NegPred(mapped) } Expr::Seq(lhs, rhs) => { let mapped_lhs = Box::new(map_internal(*lhs, f)); let mapped_rhs = Box::new(map_internal(*rhs, f)); Expr::Seq(mapped_lhs, mapped_rhs) } Expr::Choice(lhs, rhs) => { let mapped_lhs = Box::new(map_internal(*lhs, f)); let mapped_rhs = Box::new(map_internal(*rhs, f)); Expr::Choice(mapped_lhs, mapped_rhs) } Expr::Rep(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::Rep(mapped) } Expr::RepOnce(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepOnce(mapped) } Expr::RepExact(expr, num) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepExact(mapped, num) } Expr::RepMin(expr, max) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepMin(mapped, max) } Expr::RepMax(expr, max) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepMax(mapped, max) } Expr::RepMinMax(expr, min, max) => { let mapped = Box::new(map_internal(*expr, f)); Expr::RepMinMax(mapped, min, max) } Expr::Opt(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::Opt(mapped) } Expr::Push(expr) => { let mapped = Box::new(map_internal(*expr, f)); Expr::Push(mapped) } #[cfg(feature = "grammar-extras")] Expr::NodeTag(expr, tag) => { let mapped = Box::new(map_internal(*expr, f)); Expr::NodeTag(mapped, tag) } expr => expr, }; f(mapped) } map_internal(self, &mut f) } } impl core::fmt::Display for Expr { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Expr::Str(s) => write!(f, "{:?}", s), Expr::Insens(s) => write!(f, "^{:?}", s), Expr::Range(start, end) => { let start = start.chars().next().expect("Empty range start."); let end = end.chars().next().expect("Empty range end."); write!(f, "({:?}..{:?})", start, end) } Expr::Ident(id) => write!(f, "{}", id), Expr::PeekSlice(start, end) => match end { Some(end) => write!(f, "PEEK[{}..{}]", start, end), None => write!(f, "PEEK[{}..]", start), }, Expr::PosPred(expr) => write!(f, "&{}", expr.as_ref()), Expr::NegPred(expr) => write!(f, "!{}", expr.as_ref()), Expr::Seq(lhs, rhs) => { let mut nodes = Vec::new(); nodes.push(lhs); let mut current = rhs; while let Expr::Seq(lhs, rhs) = current.as_ref() { nodes.push(lhs); current = rhs; } nodes.push(current); let sequence = nodes .iter() .map(|node| format!("{}", node)) .collect::>() .join(" ~ "); write!(f, "({})", sequence) } Expr::Choice(lhs, rhs) => { let mut nodes = Vec::new(); nodes.push(lhs); let mut current = rhs; while let Expr::Choice(lhs, rhs) = current.as_ref() { nodes.push(lhs); current = rhs; } nodes.push(current); let sequence = nodes .iter() .map(|node| format!("{}", node)) .collect::>() .join(" | "); write!(f, "({})", sequence) } Expr::Opt(expr) => write!(f, "{}?", expr), Expr::Rep(expr) => write!(f, "{}*", expr), Expr::RepOnce(expr) => write!(f, "{}+", expr), Expr::RepExact(expr, n) => write!(f, "{}{{{}}}", expr, n), Expr::RepMin(expr, min) => write!(f, "{}{{{},}}", expr, min), Expr::RepMax(expr, max) => write!(f, "{}{{,{}}}", expr, max), Expr::RepMinMax(expr, min, max) => write!(f, "{}{{{}, {}}}", expr, min, max), Expr::Skip(strings) => { let strings = strings .iter() .map(|s| format!("{:?}", s)) .collect::>() .join(" | "); write!(f, "(!({}) ~ ANY)*", strings) } Expr::Push(expr) => write!(f, "PUSH({})", expr), #[cfg(feature = "grammar-extras")] Expr::NodeTag(expr, tag) => { write!(f, "(#{} = {})", tag, expr) } } } } /// The top down iterator for an expression. pub struct ExprTopDownIterator { current: Option, next: Option, right_branches: Vec, } impl ExprTopDownIterator { /// Constructs a top-down iterator from the expression. pub fn new(expr: &Expr) -> Self { let mut iter = ExprTopDownIterator { current: None, next: None, right_branches: vec![], }; iter.iterate_expr(expr.clone()); iter } fn iterate_expr(&mut self, expr: Expr) { self.current = Some(expr.clone()); match expr { Expr::Seq(lhs, rhs) => { self.right_branches.push(*rhs); self.next = Some(*lhs); } Expr::Choice(lhs, rhs) => { self.right_branches.push(*rhs); self.next = Some(*lhs); } Expr::PosPred(expr) | Expr::NegPred(expr) | Expr::Rep(expr) | Expr::RepOnce(expr) | Expr::RepExact(expr, _) | Expr::RepMin(expr, _) | Expr::RepMax(expr, _) | Expr::RepMinMax(expr, ..) | Expr::Opt(expr) | Expr::Push(expr) => { self.next = Some(*expr); } #[cfg(feature = "grammar-extras")] Expr::NodeTag(expr, _) => { self.next = Some(*expr); } _ => { self.next = None; } } } } impl Iterator for ExprTopDownIterator { type Item = Expr; fn next(&mut self) -> Option { let result = self.current.take(); if let Some(expr) = self.next.take() { self.iterate_expr(expr); } else if let Some(expr) = self.right_branches.pop() { self.iterate_expr(expr); } result } } #[cfg(test)] mod tests { use super::*; #[test] fn top_down_iterator() { let expr = Expr::Choice( Box::new(Expr::Str(String::from("a"))), Box::new(Expr::Str(String::from("b"))), ); let mut top_down = expr.iter_top_down(); assert_eq!(top_down.next(), Some(expr)); assert_eq!(top_down.next(), Some(Expr::Str(String::from("a")))); assert_eq!(top_down.next(), Some(Expr::Str(String::from("b")))); assert_eq!(top_down.next(), None); } #[test] fn identity() { let expr = Expr::Choice( Box::new(Expr::Seq( Box::new(Expr::Ident("a".to_owned())), Box::new(Expr::Str("b".to_owned())), )), Box::new(Expr::PosPred(Box::new(Expr::NegPred(Box::new(Expr::Rep( Box::new(Expr::RepOnce(Box::new(Expr::Opt(Box::new(Expr::Choice( Box::new(Expr::Insens("c".to_owned())), Box::new(Expr::Push(Box::new(Expr::Range( "'d'".to_owned(), "'e'".to_owned(), )))), )))))), )))))), ); assert_eq!( expr.clone() .map_bottom_up(|expr| expr) .map_top_down(|expr| expr), expr, ); } mod display { use super::super::*; #[test] fn string() { assert_eq!(Expr::Str("a".to_owned()).to_string(), r#""a""#); } #[test] fn insens() { assert_eq!(Expr::Insens("a".to_owned()).to_string(), r#"^"a""#); } #[test] fn range() { assert_eq!( Expr::Range("a".to_owned(), "z".to_owned()).to_string(), r#"('a'..'z')"#, ); } #[test] fn ident() { assert_eq!(Expr::Ident("a".to_owned()).to_string(), "a"); } #[test] fn peek_slice() { assert_eq!(Expr::PeekSlice(0, None).to_string(), "PEEK[0..]"); assert_eq!(Expr::PeekSlice(0, Some(-1)).to_string(), "PEEK[0..-1]"); } #[test] fn pos_pred() { assert_eq!( Expr::PosPred(Box::new(Expr::Ident("e".to_owned()))).to_string(), "&e", ); } #[test] fn neg_pred() { assert_eq!( Expr::NegPred(Box::new(Expr::Ident("e".to_owned()))).to_string(), "!e", ); } #[test] fn seq() { assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Ident("e2".to_owned())), ) .to_string(), "(e1 ~ e2)", ); assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Ident("e3".to_owned())), )), ) .to_string(), "(e1 ~ e2 ~ e3)", ); assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 ~ e2 ~ e3 ~ e4)", ); assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 ~ (e2 | (e3 ~ e4)))", ); assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 ~ e2 ~ (e3 | e4))", ); } #[test] fn choice() { assert_eq!( Expr::Choice( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Ident("e2".to_owned())), ) .to_string(), "(e1 | e2)", ); assert_eq!( Expr::Choice( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Ident("e3".to_owned())), )), ) .to_string(), "(e1 | e2 | e3)", ); assert_eq!( Expr::Choice( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 | e2 | e3 | e4)", ); assert_eq!( Expr::Choice( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 | (e2 ~ (e3 | e4)))", ); } #[test] fn opt() { assert_eq!( Expr::Opt(Box::new(Expr::Ident("e".to_owned()))).to_string(), "e?", ); } #[test] fn rep() { assert_eq!( Expr::Rep(Box::new(Expr::Ident("e".to_owned()))).to_string(), "e*", ); } #[test] fn rep_once() { assert_eq!( Expr::RepOnce(Box::new(Expr::Ident("e".to_owned()))).to_string(), "e+", ); } #[test] fn rep_exact() { assert_eq!( Expr::RepExact(Box::new(Expr::Ident("e".to_owned())), 1).to_string(), "e{1}", ); } #[test] fn rep_min() { assert_eq!( Expr::RepMin(Box::new(Expr::Ident("e".to_owned())), 1).to_string(), "e{1,}", ); } #[test] fn rep_max() { assert_eq!( Expr::RepMax(Box::new(Expr::Ident("e".to_owned())), 1).to_string(), "e{,1}", ); } #[test] fn rep_min_max() { assert_eq!( Expr::RepMinMax(Box::new(Expr::Ident("e".to_owned())), 1, 2).to_string(), "e{1, 2}", ); } #[test] fn skip() { assert_eq!( Expr::Skip( ["a", "bc"] .into_iter() .map(|s| s.to_owned()) .collect::>(), ) .to_string(), r#"(!("a" | "bc") ~ ANY)*"#, ); } #[test] fn push() { assert_eq!( Expr::Push(Box::new(Expr::Ident("e".to_owned()))).to_string(), "PUSH(e)", ); } #[test] #[cfg(feature = "grammar-extras")] fn node_tag() { assert_eq!( Expr::NodeTag(Box::new(Expr::Ident("expr".to_owned())), "label".to_owned()) .to_string(), "(#label = expr)", ); } } } pest_meta-2.7.4/src/grammar.pest000064400000000000000000000132111046102023000147270ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. //! Pest meta-grammar //! //! # Warning: Semantic Versioning //! There may be non-breaking changes to the meta-grammar //! between minor versions. Those non-breaking changes, however, //! may translate into semver-breaking changes due to the additional variants //! added to the `Rule` enum. This is a known issue and will be fixed in the //! future (e.g. by increasing MSRV and non_exhaustive annotations). /// The top-level rule of a grammar. grammar_rules = _{ SOI ~ grammar_doc* ~ grammar_rule* ~ EOI } /// A rule of a grammar. grammar_rule = { identifier ~ assignment_operator ~ modifier? ~ opening_brace ~ expression ~ closing_brace | line_doc } /// Assignment operator. assignment_operator = { "=" } /// Opening brace for a rule. opening_brace = { "{" } /// Closing brace for a rule. closing_brace = { "}" } /// Opening parenthesis for a branch, PUSH, etc. opening_paren = { "(" } /// Closing parenthesis for a branch, PUSH, etc. closing_paren = { ")" } /// Opening bracket for PEEK (slice inside). opening_brack = { "[" } /// Closing bracket for PEEK (slice inside). closing_brack = { "]" } /// A rule modifier. modifier = _{ silent_modifier | atomic_modifier | compound_atomic_modifier | non_atomic_modifier } /// Silent rule prefix. silent_modifier = { "_" } /// Atomic rule prefix. atomic_modifier = { "@" } /// Compound atomic rule prefix. compound_atomic_modifier = { "$" } /// Non-atomic rule prefix. non_atomic_modifier = { "!" } /// A tag label. tag_id = @{ "#" ~ ("_" | alpha) ~ ("_" | alpha_num)* } /// For assigning labels to nodes. node_tag = _{ tag_id ~ assignment_operator } /// A rule expression. expression = { choice_operator? ~ term ~ (infix_operator ~ term)* } /// A rule term. term = { node_tag? ~ prefix_operator* ~ node ~ postfix_operator* } /// A rule node (inside terms). node = _{ opening_paren ~ expression ~ closing_paren | terminal } /// A terminal expression. terminal = _{ _push | peek_slice | identifier | string | insensitive_string | range } /// Possible predicates for a rule. prefix_operator = _{ positive_predicate_operator | negative_predicate_operator } /// Branches or sequences. infix_operator = _{ sequence_operator | choice_operator } /// Possible modifiers for a rule. postfix_operator = _{ optional_operator | repeat_operator | repeat_once_operator | repeat_exact | repeat_min | repeat_max | repeat_min_max } /// A positive predicate. positive_predicate_operator = { "&" } /// A negative predicate. negative_predicate_operator = { "!" } /// A sequence operator. sequence_operator = { "~" } /// A choice operator. choice_operator = { "|" } /// An optional operator. optional_operator = { "?" } /// A repeat operator. repeat_operator = { "*" } /// A repeat at least once operator. repeat_once_operator = { "+" } /// A repeat exact times. repeat_exact = { opening_brace ~ number ~ closing_brace } /// A repeat at least times. repeat_min = { opening_brace ~ number ~ comma ~ closing_brace } /// A repeat at most times. repeat_max = { opening_brace ~ comma ~ number ~ closing_brace } /// A repeat in a range. repeat_min_max = { opening_brace ~ number ~ comma ~ number ~ closing_brace } /// A number. number = @{ '0'..'9'+ } /// An integer number (positive or negative). integer = @{ number | "-" ~ "0"* ~ '1'..'9' ~ number? } /// A comma terminal. comma = { "," } /// A PUSH expression. _push = { "PUSH" ~ opening_paren ~ expression ~ closing_paren } /// A PEEK expression. peek_slice = { "PEEK" ~ opening_brack ~ integer? ~ range_operator ~ integer? ~ closing_brack } /// An identifier. identifier = @{ !"PUSH" ~ ("_" | alpha) ~ ("_" | alpha_num)* } /// An alpha character. alpha = _{ 'a'..'z' | 'A'..'Z' } /// An alphanumeric character. alpha_num = _{ alpha | '0'..'9' } /// A string. string = ${ quote ~ inner_str ~ quote } /// An insensitive string. insensitive_string = { "^" ~ string } /// A character range. range = { character ~ range_operator ~ character } /// A single quoted character character = ${ single_quote ~ inner_chr ~ single_quote } /// A quoted string. inner_str = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ inner_str)? } /// An escaped or any character. inner_chr = @{ escape | ANY } /// An escape sequence. escape = @{ "\\" ~ ("\"" | "\\" | "r" | "n" | "t" | "0" | "'" | code | unicode) } /// A hexadecimal code. code = @{ "x" ~ hex_digit{2} } /// A unicode code. unicode = @{ "u" ~ opening_brace ~ hex_digit{2, 6} ~ closing_brace } /// A hexadecimal digit. hex_digit = @{ '0'..'9' | 'a'..'f' | 'A'..'F' } /// A double quote. quote = { "\"" } /// A single quote. single_quote = { "'" } /// A range operator. range_operator = { ".." } /// A newline character. newline = _{ "\n" | "\r\n" } /// A whitespace character. WHITESPACE = _{ " " | "\t" | newline } /// A single line comment. line_comment = _{ ("//" ~ !("/" | "!") ~ (!newline ~ ANY)*) } /// A multi-line comment. block_comment = _{ "/*" ~ (block_comment | !"*/" ~ ANY)* ~ "*/" } /// A grammar comment. COMMENT = _{ block_comment | line_comment } // ref: https://doc.rust-lang.org/reference/comments.html /// A space character. space = _{ " " | "\t" } /// A top-level comment. grammar_doc = ${ "//!" ~ space? ~ inner_doc } /// A rule comment. line_doc = ${ "///" ~ space? ~ inner_doc } /// A comment content. inner_doc = @{ (!newline ~ ANY)* } pest_meta-2.7.4/src/grammar.rs000064400000000000000000001271711046102023000144130ustar 00000000000000pub struct PestParser; # [doc = "Pest meta-grammar\n\n# Warning: Semantic Versioning\nThere may be non-breaking changes to the meta-grammar\nbetween minor versions. Those non-breaking changes, however,\nmay translate into semver-breaking changes due to the additional variants\nadded to the `Rule` enum. This is a known issue and will be fixed in the\nfuture (e.g. by increasing MSRV and non_exhaustive annotations)."] # [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] # [derive (Clone , Copy , Debug , Eq , Hash , Ord , PartialEq , PartialOrd)] pub enum Rule { EOI , # [doc = "The top-level rule of a grammar."] r#grammar_rules , # [doc = "A rule of a grammar."] r#grammar_rule , # [doc = "Assignment operator."] r#assignment_operator , # [doc = "Opening brace for a rule."] r#opening_brace , # [doc = "Closing brace for a rule."] r#closing_brace , # [doc = "Opening parenthesis for a branch, PUSH, etc."] r#opening_paren , # [doc = "Closing parenthesis for a branch, PUSH, etc."] r#closing_paren , # [doc = "Opening bracket for PEEK (slice inside)."] r#opening_brack , # [doc = "Closing bracket for PEEK (slice inside)."] r#closing_brack , # [doc = "A rule modifier."] r#modifier , # [doc = "Silent rule prefix."] r#silent_modifier , # [doc = "Atomic rule prefix."] r#atomic_modifier , # [doc = "Compound atomic rule prefix."] r#compound_atomic_modifier , # [doc = "Non-atomic rule prefix."] r#non_atomic_modifier , # [doc = "A tag label."] r#tag_id , # [doc = "For assigning labels to nodes."] r#node_tag , # [doc = "A rule expression."] r#expression , # [doc = "A rule term."] r#term , # [doc = "A rule node (inside terms)."] r#node , # [doc = "A terminal expression."] r#terminal , # [doc = "Possible predicates for a rule."] r#prefix_operator , # [doc = "Branches or sequences."] r#infix_operator , # [doc = "Possible modifiers for a rule."] r#postfix_operator , # [doc = "A positive predicate."] r#positive_predicate_operator , # [doc = "A negative predicate."] r#negative_predicate_operator , # [doc = "A sequence operator."] r#sequence_operator , # [doc = "A choice operator."] r#choice_operator , # [doc = "An optional operator."] r#optional_operator , # [doc = "A repeat operator."] r#repeat_operator , # [doc = "A repeat at least once operator."] r#repeat_once_operator , # [doc = "A repeat exact times."] r#repeat_exact , # [doc = "A repeat at least times."] r#repeat_min , # [doc = "A repeat at most times."] r#repeat_max , # [doc = "A repeat in a range."] r#repeat_min_max , # [doc = "A number."] r#number , # [doc = "An integer number (positive or negative)."] r#integer , # [doc = "A comma terminal."] r#comma , # [doc = "A PUSH expression."] r#_push , # [doc = "A PEEK expression."] r#peek_slice , # [doc = "An identifier."] r#identifier , # [doc = "An alpha character."] r#alpha , # [doc = "An alphanumeric character."] r#alpha_num , # [doc = "A string."] r#string , # [doc = "An insensitive string."] r#insensitive_string , # [doc = "A character range."] r#range , # [doc = "A single quoted character"] r#character , # [doc = "A quoted string."] r#inner_str , # [doc = "An escaped or any character."] r#inner_chr , # [doc = "An escape sequence."] r#escape , # [doc = "A hexadecimal code."] r#code , # [doc = "A unicode code."] r#unicode , # [doc = "A hexadecimal digit."] r#hex_digit , # [doc = "A double quote."] r#quote , # [doc = "A single quote."] r#single_quote , # [doc = "A range operator."] r#range_operator , # [doc = "A newline character."] r#newline , # [doc = "A whitespace character."] r#WHITESPACE , # [doc = "A single line comment."] r#line_comment , # [doc = "A multi-line comment."] r#block_comment , # [doc = "A grammar comment."] r#COMMENT , # [doc = "A space character."] r#space , # [doc = "A top-level comment."] r#grammar_doc , # [doc = "A rule comment."] r#line_doc , # [doc = "A comment content."] r#inner_doc } # [allow (clippy :: all)] impl :: pest :: Parser < Rule > for PestParser { fn parse < 'i > (rule : Rule , input : & 'i str) -> :: std :: result :: Result < :: pest :: iterators :: Pairs < 'i , Rule > , :: pest :: error :: Error < Rule > > { mod rules { # ! [allow (clippy :: upper_case_acronyms)] pub mod hidden { use super :: super :: Rule ; # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn skip (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { if state . atomicity () == :: pest :: Atomicity :: NonAtomic { state . sequence (| state | { state . repeat (| state | super :: visible :: WHITESPACE (state)) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: visible :: COMMENT (state) . and_then (| state | { state . repeat (| state | super :: visible :: WHITESPACE (state)) }) }) }) }) }) } else { Ok (state) } } } pub mod visible { use super :: super :: Rule ; # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#grammar_rules (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . sequence (| state | { self :: r#SOI (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#grammar_doc (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#grammar_doc (state) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#grammar_rule (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#grammar_rule (state) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#EOI (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#grammar_rule (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#grammar_rule , | state | { state . sequence (| state | { self :: r#identifier (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#assignment_operator (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#modifier (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#opening_brace (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#expression (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#closing_brace (state) }) }) . or_else (| state | { self :: r#line_doc (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#assignment_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#assignment_operator , | state | { state . match_string ("=") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#opening_brace (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#opening_brace , | state | { state . match_string ("{") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#closing_brace (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#closing_brace , | state | { state . match_string ("}") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#opening_paren (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#opening_paren , | state | { state . match_string ("(") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#closing_paren (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#closing_paren , | state | { state . match_string (")") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#opening_brack (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#opening_brack , | state | { state . match_string ("[") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#closing_brack (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#closing_brack , | state | { state . match_string ("]") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#modifier (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { self :: r#silent_modifier (state) . or_else (| state | { self :: r#atomic_modifier (state) }) . or_else (| state | { self :: r#compound_atomic_modifier (state) }) . or_else (| state | { self :: r#non_atomic_modifier (state) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#silent_modifier (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#silent_modifier , | state | { state . match_string ("_") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#atomic_modifier (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#atomic_modifier , | state | { state . match_string ("@") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#compound_atomic_modifier (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#compound_atomic_modifier , | state | { state . match_string ("$") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#non_atomic_modifier (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#non_atomic_modifier , | state | { state . match_string ("!") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#tag_id (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#tag_id , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { state . match_string ("#") . and_then (| state | { state . match_string ("_") . or_else (| state | { self :: r#alpha (state) }) }) . and_then (| state | { state . repeat (| state | { state . match_string ("_") . or_else (| state | { self :: r#alpha_num (state) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#node_tag (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . sequence (| state | { self :: r#tag_id (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#assignment_operator (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#expression (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#expression , | state | { state . sequence (| state | { state . optional (| state | { self :: r#choice_operator (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#term (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { state . sequence (| state | { self :: r#infix_operator (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#term (state) }) }) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { state . sequence (| state | { self :: r#infix_operator (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#term (state) }) }) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#term (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#term , | state | { state . sequence (| state | { state . optional (| state | { self :: r#node_tag (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#prefix_operator (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#prefix_operator (state) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#node (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#postfix_operator (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#postfix_operator (state) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#node (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . sequence (| state | { self :: r#opening_paren (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#expression (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#closing_paren (state) }) }) . or_else (| state | { self :: r#terminal (state) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#terminal (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { self :: r#_push (state) . or_else (| state | { self :: r#peek_slice (state) }) . or_else (| state | { self :: r#identifier (state) }) . or_else (| state | { self :: r#string (state) }) . or_else (| state | { self :: r#insensitive_string (state) }) . or_else (| state | { self :: r#range (state) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#prefix_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { self :: r#positive_predicate_operator (state) . or_else (| state | { self :: r#negative_predicate_operator (state) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#infix_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { self :: r#sequence_operator (state) . or_else (| state | { self :: r#choice_operator (state) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#postfix_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { self :: r#optional_operator (state) . or_else (| state | { self :: r#repeat_operator (state) }) . or_else (| state | { self :: r#repeat_once_operator (state) }) . or_else (| state | { self :: r#repeat_exact (state) }) . or_else (| state | { self :: r#repeat_min (state) }) . or_else (| state | { self :: r#repeat_max (state) }) . or_else (| state | { self :: r#repeat_min_max (state) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#positive_predicate_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#positive_predicate_operator , | state | { state . match_string ("&") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#negative_predicate_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#negative_predicate_operator , | state | { state . match_string ("!") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#sequence_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#sequence_operator , | state | { state . match_string ("~") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#choice_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#choice_operator , | state | { state . match_string ("|") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#optional_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#optional_operator , | state | { state . match_string ("?") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#repeat_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#repeat_operator , | state | { state . match_string ("*") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#repeat_once_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#repeat_once_operator , | state | { state . match_string ("+") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#repeat_exact (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#repeat_exact , | state | { state . sequence (| state | { self :: r#opening_brace (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#number (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#closing_brace (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#repeat_min (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#repeat_min , | state | { state . sequence (| state | { self :: r#opening_brace (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#number (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#comma (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#closing_brace (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#repeat_max (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#repeat_max , | state | { state . sequence (| state | { self :: r#opening_brace (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#comma (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#number (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#closing_brace (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#repeat_min_max (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#repeat_min_max , | state | { state . sequence (| state | { self :: r#opening_brace (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#number (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#comma (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#number (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#closing_brace (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#number (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#number , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { state . match_range ('0' .. '9') . and_then (| state | { state . repeat (| state | { state . match_range ('0' .. '9') }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#integer (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#integer , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { self :: r#number (state) . or_else (| state | { state . sequence (| state | { state . match_string ("-") . and_then (| state | { state . repeat (| state | { state . match_string ("0") }) }) . and_then (| state | { state . match_range ('1' .. '9') }) . and_then (| state | { state . optional (| state | { self :: r#number (state) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#comma (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#comma , | state | { state . match_string (",") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#_push (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#_push , | state | { state . sequence (| state | { state . match_string ("PUSH") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#opening_paren (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#expression (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#closing_paren (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#peek_slice (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#peek_slice , | state | { state . sequence (| state | { state . match_string ("PEEK") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#opening_brack (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#integer (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#range_operator (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#integer (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#closing_brack (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#identifier (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#identifier , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { state . lookahead (false , | state | { state . match_string ("PUSH") }) . and_then (| state | { state . match_string ("_") . or_else (| state | { self :: r#alpha (state) }) }) . and_then (| state | { state . repeat (| state | { state . match_string ("_") . or_else (| state | { self :: r#alpha_num (state) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#alpha (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . match_range ('a' .. 'z') . or_else (| state | { state . match_range ('A' .. 'Z') }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#alpha_num (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { self :: r#alpha (state) . or_else (| state | { state . match_range ('0' .. '9') }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#string (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: CompoundAtomic , | state | { state . rule (Rule :: r#string , | state | { state . sequence (| state | { self :: r#quote (state) . and_then (| state | { self :: r#inner_str (state) }) . and_then (| state | { self :: r#quote (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#insensitive_string (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#insensitive_string , | state | { state . sequence (| state | { state . match_string ("^") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#string (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#range (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#range , | state | { state . sequence (| state | { self :: r#character (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#range_operator (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#character (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#character (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: CompoundAtomic , | state | { state . rule (Rule :: r#character , | state | { state . sequence (| state | { self :: r#single_quote (state) . and_then (| state | { self :: r#inner_chr (state) }) . and_then (| state | { self :: r#single_quote (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#inner_str (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#inner_str , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { let strings = ["\"" , "\\"] ; state . skip_until (& strings) . and_then (| state | { state . optional (| state | { state . sequence (| state | { self :: r#escape (state) . and_then (| state | { self :: r#inner_str (state) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#inner_chr (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#inner_chr , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { self :: r#escape (state) . or_else (| state | { self :: r#ANY (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#escape (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#escape , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { state . match_string ("\\") . and_then (| state | { state . match_string ("\"") . or_else (| state | { state . match_string ("\\") }) . or_else (| state | { state . match_string ("r") }) . or_else (| state | { state . match_string ("n") }) . or_else (| state | { state . match_string ("t") }) . or_else (| state | { state . match_string ("0") }) . or_else (| state | { state . match_string ("'") }) . or_else (| state | { self :: r#code (state) }) . or_else (| state | { self :: r#unicode (state) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#code (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#code , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { state . match_string ("x") . and_then (| state | { self :: r#hex_digit (state) }) . and_then (| state | { self :: r#hex_digit (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#unicode (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#unicode , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { state . match_string ("u") . and_then (| state | { self :: r#opening_brace (state) }) . and_then (| state | { state . sequence (| state | { self :: r#hex_digit (state) . and_then (| state | { self :: r#hex_digit (state) }) . and_then (| state | { state . optional (| state | { self :: r#hex_digit (state) }) }) . and_then (| state | { state . optional (| state | { self :: r#hex_digit (state) }) }) . and_then (| state | { state . optional (| state | { self :: r#hex_digit (state) }) }) . and_then (| state | { state . optional (| state | { self :: r#hex_digit (state) }) }) }) }) . and_then (| state | { self :: r#closing_brace (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#hex_digit (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#hex_digit , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . match_range ('0' .. '9') . or_else (| state | { state . match_range ('a' .. 'f') }) . or_else (| state | { state . match_range ('A' .. 'F') }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#quote (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#quote , | state | { state . match_string ("\"") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#single_quote (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#single_quote , | state | { state . match_string ("'") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#range_operator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#range_operator , | state | { state . match_string ("..") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#newline (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . match_string ("\n") . or_else (| state | { state . match_string ("\r\n") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#WHITESPACE (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . match_string (" ") . or_else (| state | { state . match_string ("\t") }) . or_else (| state | { self :: r#newline (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#line_comment (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . sequence (| state | { state . match_string ("//") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . lookahead (false , | state | { state . match_string ("/") . or_else (| state | { state . match_string ("!") }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { state . sequence (| state | { state . lookahead (false , | state | { self :: r#newline (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ANY (state) }) }) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { state . sequence (| state | { state . lookahead (false , | state | { self :: r#newline (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ANY (state) }) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#block_comment (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . sequence (| state | { state . match_string ("/*") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#block_comment (state) . or_else (| state | { state . sequence (| state | { state . lookahead (false , | state | { state . match_string ("*/") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ANY (state) }) }) }) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#block_comment (state) . or_else (| state | { state . sequence (| state | { state . lookahead (false , | state | { state . match_string ("*/") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ANY (state) }) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("*/") }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#COMMENT (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: Atomic , | state | { self :: r#block_comment (state) . or_else (| state | { self :: r#line_comment (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#space (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . match_string (" ") . or_else (| state | { state . match_string ("\t") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#grammar_doc (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: CompoundAtomic , | state | { state . rule (Rule :: r#grammar_doc , | state | { state . sequence (| state | { state . match_string ("//!") . and_then (| state | { state . optional (| state | { self :: r#space (state) }) }) . and_then (| state | { self :: r#inner_doc (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#line_doc (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: CompoundAtomic , | state | { state . rule (Rule :: r#line_doc , | state | { state . sequence (| state | { state . match_string ("///") . and_then (| state | { state . optional (| state | { self :: r#space (state) }) }) . and_then (| state | { self :: r#inner_doc (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#inner_doc (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#inner_doc , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . repeat (| state | { state . sequence (| state | { state . lookahead (false , | state | { self :: r#newline (state) }) . and_then (| state | { self :: r#ANY (state) }) }) }) }) }) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn ANY (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . skip (1) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn EOI (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: EOI , | state | state . end_of_input ()) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn SOI (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . start_of_input () } } pub use self :: visible :: * ; } :: pest :: state (input , | state | { match rule { Rule :: r#grammar_rules => rules :: r#grammar_rules (state) , Rule :: r#grammar_rule => rules :: r#grammar_rule (state) , Rule :: r#assignment_operator => rules :: r#assignment_operator (state) , Rule :: r#opening_brace => rules :: r#opening_brace (state) , Rule :: r#closing_brace => rules :: r#closing_brace (state) , Rule :: r#opening_paren => rules :: r#opening_paren (state) , Rule :: r#closing_paren => rules :: r#closing_paren (state) , Rule :: r#opening_brack => rules :: r#opening_brack (state) , Rule :: r#closing_brack => rules :: r#closing_brack (state) , Rule :: r#modifier => rules :: r#modifier (state) , Rule :: r#silent_modifier => rules :: r#silent_modifier (state) , Rule :: r#atomic_modifier => rules :: r#atomic_modifier (state) , Rule :: r#compound_atomic_modifier => rules :: r#compound_atomic_modifier (state) , Rule :: r#non_atomic_modifier => rules :: r#non_atomic_modifier (state) , Rule :: r#tag_id => rules :: r#tag_id (state) , Rule :: r#node_tag => rules :: r#node_tag (state) , Rule :: r#expression => rules :: r#expression (state) , Rule :: r#term => rules :: r#term (state) , Rule :: r#node => rules :: r#node (state) , Rule :: r#terminal => rules :: r#terminal (state) , Rule :: r#prefix_operator => rules :: r#prefix_operator (state) , Rule :: r#infix_operator => rules :: r#infix_operator (state) , Rule :: r#postfix_operator => rules :: r#postfix_operator (state) , Rule :: r#positive_predicate_operator => rules :: r#positive_predicate_operator (state) , Rule :: r#negative_predicate_operator => rules :: r#negative_predicate_operator (state) , Rule :: r#sequence_operator => rules :: r#sequence_operator (state) , Rule :: r#choice_operator => rules :: r#choice_operator (state) , Rule :: r#optional_operator => rules :: r#optional_operator (state) , Rule :: r#repeat_operator => rules :: r#repeat_operator (state) , Rule :: r#repeat_once_operator => rules :: r#repeat_once_operator (state) , Rule :: r#repeat_exact => rules :: r#repeat_exact (state) , Rule :: r#repeat_min => rules :: r#repeat_min (state) , Rule :: r#repeat_max => rules :: r#repeat_max (state) , Rule :: r#repeat_min_max => rules :: r#repeat_min_max (state) , Rule :: r#number => rules :: r#number (state) , Rule :: r#integer => rules :: r#integer (state) , Rule :: r#comma => rules :: r#comma (state) , Rule :: r#_push => rules :: r#_push (state) , Rule :: r#peek_slice => rules :: r#peek_slice (state) , Rule :: r#identifier => rules :: r#identifier (state) , Rule :: r#alpha => rules :: r#alpha (state) , Rule :: r#alpha_num => rules :: r#alpha_num (state) , Rule :: r#string => rules :: r#string (state) , Rule :: r#insensitive_string => rules :: r#insensitive_string (state) , Rule :: r#range => rules :: r#range (state) , Rule :: r#character => rules :: r#character (state) , Rule :: r#inner_str => rules :: r#inner_str (state) , Rule :: r#inner_chr => rules :: r#inner_chr (state) , Rule :: r#escape => rules :: r#escape (state) , Rule :: r#code => rules :: r#code (state) , Rule :: r#unicode => rules :: r#unicode (state) , Rule :: r#hex_digit => rules :: r#hex_digit (state) , Rule :: r#quote => rules :: r#quote (state) , Rule :: r#single_quote => rules :: r#single_quote (state) , Rule :: r#range_operator => rules :: r#range_operator (state) , Rule :: r#newline => rules :: r#newline (state) , Rule :: r#WHITESPACE => rules :: r#WHITESPACE (state) , Rule :: r#line_comment => rules :: r#line_comment (state) , Rule :: r#block_comment => rules :: r#block_comment (state) , Rule :: r#COMMENT => rules :: r#COMMENT (state) , Rule :: r#space => rules :: r#space (state) , Rule :: r#grammar_doc => rules :: r#grammar_doc (state) , Rule :: r#line_doc => rules :: r#line_doc (state) , Rule :: r#inner_doc => rules :: r#inner_doc (state) , Rule :: EOI => rules :: EOI (state) } }) } } pest_meta-2.7.4/src/lib.rs000064400000000000000000000046611046102023000135310ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. //! # pest meta //! //! This crate parses, validates, optimizes, and converts pest's own grammars to ASTs. #![doc( html_logo_url = "https://raw.githubusercontent.com/pest-parser/pest/master/pest-logo.svg", html_favicon_url = "https://raw.githubusercontent.com/pest-parser/pest/master/pest-logo.svg" )] #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] #[cfg(test)] #[macro_use] extern crate pest; use once_cell::sync::Lazy; use std::fmt::Display; use pest::error::Error; use pest::unicode::unicode_property_names; pub mod ast; pub mod optimizer; pub mod parser; pub mod validator; /// A helper that will unwrap the result or panic /// with the nicely formatted error message. pub fn unwrap_or_report(result: Result) -> T where E: IntoIterator, E::Item: Display, { result.unwrap_or_else(|e| { panic!( "{}{}", "grammar error\n\n".to_owned(), &e.into_iter() .map(|error| format!("{}", error)) .collect::>() .join("\n\n") ) }) } /// A tuple returned by the validation and processing of the parsed grammar. /// The first element is the vector of used builtin rule names, /// the second element is the vector of optimized rules. type UsedBuiltinAndOptimized<'i> = (Vec<&'i str>, Vec); /// Parses, validates, processes and optimizes the provided grammar. pub fn parse_and_optimize( grammar: &str, ) -> Result, Vec>> { let pairs = match parser::parse(parser::Rule::grammar_rules, grammar) { Ok(pairs) => Ok(pairs), Err(error) => Err(vec![error]), }?; let defaults = validator::validate_pairs(pairs.clone())?; let ast = parser::consume_rules(pairs)?; Ok((defaults, optimizer::optimize(ast))) } #[doc(hidden)] #[deprecated(note = "use `pest::unicode::unicode_property_names` instead")] pub static UNICODE_PROPERTY_NAMES: Lazy> = Lazy::new(|| unicode_property_names().collect::>()); pest_meta-2.7.4/src/optimizer/concatenator.rs000064400000000000000000000021451046102023000174600ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. use crate::ast::*; pub fn concatenate(rule: Rule) -> Rule { let Rule { name, ty, expr } = rule; Rule { name, ty, expr: expr.map_bottom_up(|expr| { if ty == RuleType::Atomic { match expr { Expr::Seq(lhs, rhs) => match (*lhs, *rhs) { (Expr::Str(lhs), Expr::Str(rhs)) => Expr::Str(lhs + &rhs), (Expr::Insens(lhs), Expr::Insens(rhs)) => Expr::Insens(lhs + &rhs), (lhs, rhs) => Expr::Seq(Box::new(lhs), Box::new(rhs)), }, expr => expr, } } else { expr } }), } } pest_meta-2.7.4/src/optimizer/factorizer.rs000064400000000000000000000044651046102023000171570ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. use crate::ast::*; pub fn factor(rule: Rule) -> Rule { let Rule { name, ty, expr } = rule; Rule { name, ty, expr: expr.map_top_down(|expr| { match expr { Expr::Choice(lhs, rhs) => match (*lhs, *rhs) { (Expr::Seq(l1, r1), Expr::Seq(l2, r2)) => { if l1 == l2 { Expr::Seq(l1, Box::new(Expr::Choice(r1, r2))) } else { Expr::Choice(Box::new(Expr::Seq(l1, r1)), Box::new(Expr::Seq(l2, r2))) } } // Converts `(rule ~ rest) | rule` to `rule ~ rest?`, avoiding trying to match `rule` twice. // This is only done for atomic rules, because other rule types have implicit whitespaces. // FIXME: "desugar" implicit whitespace rules before applying any optimizations (Expr::Seq(l1, l2), r) if matches!(ty, RuleType::Atomic | RuleType::CompoundAtomic) => { if *l1 == r { Expr::Seq(l1, Box::new(Expr::Opt(l2))) } else { Expr::Choice(Box::new(Expr::Seq(l1, l2)), Box::new(r)) } } // Converts `rule | (rule ~ rest)` to `rule` since `(rule ~ rest)` // will never match if `rule` didn't. (l, Expr::Seq(r1, r2)) => { if l == *r1 { l } else { Expr::Choice(Box::new(l), Box::new(Expr::Seq(r1, r2))) } } (lhs, rhs) => Expr::Choice(Box::new(lhs), Box::new(rhs)), }, expr => expr, } }), } } pest_meta-2.7.4/src/optimizer/lister.rs000064400000000000000000000031061046102023000163000ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. use crate::ast::*; pub fn list(rule: Rule) -> Rule { let Rule { name, ty, expr } = rule; Rule { name, ty, expr: expr.map_bottom_up(|expr| { match expr { Expr::Seq(l, r) => match *l { Expr::Rep(l) => { let l = *l; match l { Expr::Seq(l1, l2) => { // Converts `(rule ~ rest)* ~ rule` to `rule ~ (rest ~ rule)*`, // avoiding matching the last `rule` twice. if l1 == r { Expr::Seq(l1, Box::new(Expr::Rep(Box::new(Expr::Seq(l2, r))))) } else { Expr::Seq(Box::new(Expr::Rep(Box::new(Expr::Seq(l1, l2)))), r) } } expr => Expr::Seq(Box::new(Expr::Rep(Box::new(expr))), r), } } expr => Expr::Seq(Box::new(expr), r), }, expr => expr, } }), } } pest_meta-2.7.4/src/optimizer/mod.rs000064400000000000000000001124371046102023000155650ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. //! Different optimizations for pest's ASTs. use crate::ast::*; use std::collections::HashMap; #[cfg(test)] macro_rules! box_tree { ( $node:ident( $( $child:ident( $($args:tt)* ) ),+ ) ) => ( $node ( $( Box::new( box_tree!( $child ( $($args )* ) ) ) ),+ ) ); ($expr:expr) => ($expr); } mod concatenator; mod factorizer; mod lister; mod restorer; mod rotater; mod skipper; mod unroller; /// Takes pest's ASTs and optimizes them pub fn optimize(rules: Vec) -> Vec { let optimized: Vec = rules .into_iter() .map(rotater::rotate) .map(skipper::skip) .map(unroller::unroll) .map(concatenator::concatenate) .map(factorizer::factor) .map(lister::list) .map(rule_to_optimized_rule) .collect(); let rules = to_hash_map(&optimized); optimized .into_iter() .map(|rule| restorer::restore_on_err(rule, &rules)) .collect() } fn rule_to_optimized_rule(rule: Rule) -> OptimizedRule { fn to_optimized(expr: Expr) -> OptimizedExpr { match expr { Expr::Str(string) => OptimizedExpr::Str(string), Expr::Insens(string) => OptimizedExpr::Insens(string), Expr::Range(start, end) => OptimizedExpr::Range(start, end), Expr::Ident(ident) => OptimizedExpr::Ident(ident), Expr::PeekSlice(start, end) => OptimizedExpr::PeekSlice(start, end), Expr::PosPred(expr) => OptimizedExpr::PosPred(Box::new(to_optimized(*expr))), Expr::NegPred(expr) => OptimizedExpr::NegPred(Box::new(to_optimized(*expr))), Expr::Seq(lhs, rhs) => { OptimizedExpr::Seq(Box::new(to_optimized(*lhs)), Box::new(to_optimized(*rhs))) } Expr::Choice(lhs, rhs) => { OptimizedExpr::Choice(Box::new(to_optimized(*lhs)), Box::new(to_optimized(*rhs))) } Expr::Opt(expr) => OptimizedExpr::Opt(Box::new(to_optimized(*expr))), Expr::Rep(expr) => OptimizedExpr::Rep(Box::new(to_optimized(*expr))), Expr::Skip(strings) => OptimizedExpr::Skip(strings), Expr::Push(expr) => OptimizedExpr::Push(Box::new(to_optimized(*expr))), #[cfg(feature = "grammar-extras")] Expr::NodeTag(expr, tag) => OptimizedExpr::NodeTag(Box::new(to_optimized(*expr)), tag), #[cfg(feature = "grammar-extras")] Expr::RepOnce(expr) => OptimizedExpr::RepOnce(Box::new(to_optimized(*expr))), #[cfg(not(feature = "grammar-extras"))] Expr::RepOnce(_) => unreachable!("No valid transformation to OptimizedRule"), Expr::RepExact(..) | Expr::RepMin(..) | Expr::RepMax(..) | Expr::RepMinMax(..) => { unreachable!("No valid transformation to OptimizedRule") } } } OptimizedRule { name: rule.name, ty: rule.ty, expr: to_optimized(rule.expr), } } fn to_hash_map(rules: &[OptimizedRule]) -> HashMap { rules .iter() .map(|r| (r.name.clone(), r.expr.clone())) .collect() } /// The optimized version of the pest AST's `Rule`. #[derive(Clone, Debug, Eq, PartialEq)] pub struct OptimizedRule { /// The name of the rule. pub name: String, /// The type of the rule. pub ty: RuleType, /// The optimized expression of the rule. pub expr: OptimizedExpr, } /// The optimized version of the pest AST's `Expr`. /// /// # Warning: Semantic Versioning /// There may be non-breaking changes to the meta-grammar /// between minor versions. Those non-breaking changes, however, /// may translate into semver-breaking changes due to the additional variants /// propaged from the `Rule` enum. This is a known issue and will be fixed in the /// future (e.g. by increasing MSRV and non_exhaustive annotations). #[derive(Clone, Debug, Eq, PartialEq)] pub enum OptimizedExpr { /// Matches an exact string, e.g. `"a"` Str(String), /// Matches an exact string, case insensitively (ASCII only), e.g. `^"a"` Insens(String), /// Matches one character in the range, e.g. `'a'..'z'` Range(String, String), /// Matches the rule with the given name, e.g. `a` Ident(String), /// Matches a custom part of the stack, e.g. `PEEK[..]` PeekSlice(i32, Option), /// Positive lookahead; matches expression without making progress, e.g. `&e` PosPred(Box), /// Negative lookahead; matches if expression doesn't match, without making progress, e.g. `!e` NegPred(Box), /// Matches a sequence of two expressions, e.g. `e1 ~ e2` Seq(Box, Box), /// Matches either of two expressions, e.g. `e1 | e2` Choice(Box, Box), /// Optionally matches an expression, e.g. `e?` Opt(Box), /// Matches an expression zero or more times, e.g. `e*` Rep(Box), /// Matches an expression one or more times, e.g. `e+` #[cfg(feature = "grammar-extras")] RepOnce(Box), /// Continues to match expressions until one of the strings in the `Vec` is found Skip(Vec), /// Matches an expression and pushes it to the stack, e.g. `push(e)` Push(Box), /// Matches an expression and assigns a label to it, e.g. #label = exp #[cfg(feature = "grammar-extras")] NodeTag(Box, String), /// Restores an expression's checkpoint RestoreOnErr(Box), } impl OptimizedExpr { /// Returns a top-down iterator over the `OptimizedExpr`. pub fn iter_top_down(&self) -> OptimizedExprTopDownIterator { OptimizedExprTopDownIterator::new(self) } /// Applies `f` to the `OptimizedExpr` top-down. pub fn map_top_down(self, mut f: F) -> OptimizedExpr where F: FnMut(OptimizedExpr) -> OptimizedExpr, { fn map_internal(expr: OptimizedExpr, f: &mut F) -> OptimizedExpr where F: FnMut(OptimizedExpr) -> OptimizedExpr, { let expr = f(expr); match expr { OptimizedExpr::PosPred(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::PosPred(mapped) } OptimizedExpr::NegPred(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::NegPred(mapped) } OptimizedExpr::Seq(lhs, rhs) => { let mapped_lhs = Box::new(map_internal(*lhs, f)); let mapped_rhs = Box::new(map_internal(*rhs, f)); OptimizedExpr::Seq(mapped_lhs, mapped_rhs) } OptimizedExpr::Choice(lhs, rhs) => { let mapped_lhs = Box::new(map_internal(*lhs, f)); let mapped_rhs = Box::new(map_internal(*rhs, f)); OptimizedExpr::Choice(mapped_lhs, mapped_rhs) } OptimizedExpr::Rep(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::Rep(mapped) } OptimizedExpr::Opt(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::Opt(mapped) } OptimizedExpr::Push(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::Push(mapped) } expr => expr, } } map_internal(self, &mut f) } /// Applies `f` to the `OptimizedExpr` bottom-up. pub fn map_bottom_up(self, mut f: F) -> OptimizedExpr where F: FnMut(OptimizedExpr) -> OptimizedExpr, { fn map_internal(expr: OptimizedExpr, f: &mut F) -> OptimizedExpr where F: FnMut(OptimizedExpr) -> OptimizedExpr, { let mapped = match expr { OptimizedExpr::PosPred(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::PosPred(mapped) } OptimizedExpr::NegPred(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::NegPred(mapped) } OptimizedExpr::Seq(lhs, rhs) => { let mapped_lhs = Box::new(map_internal(*lhs, f)); let mapped_rhs = Box::new(map_internal(*rhs, f)); OptimizedExpr::Seq(mapped_lhs, mapped_rhs) } OptimizedExpr::Choice(lhs, rhs) => { let mapped_lhs = Box::new(map_internal(*lhs, f)); let mapped_rhs = Box::new(map_internal(*rhs, f)); OptimizedExpr::Choice(mapped_lhs, mapped_rhs) } OptimizedExpr::Rep(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::Rep(mapped) } OptimizedExpr::Opt(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::Opt(mapped) } OptimizedExpr::Push(expr) => { let mapped = Box::new(map_internal(*expr, f)); OptimizedExpr::Push(mapped) } expr => expr, }; f(mapped) } map_internal(self, &mut f) } } impl core::fmt::Display for OptimizedExpr { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { OptimizedExpr::Str(s) => write!(f, "{:?}", s), OptimizedExpr::Insens(s) => write!(f, "^{:?}", s), OptimizedExpr::Range(start, end) => { let start = start.chars().next().expect("Empty range start."); let end = end.chars().next().expect("Empty range end."); write!(f, "({:?}..{:?})", start, end) } OptimizedExpr::Ident(id) => write!(f, "{}", id), OptimizedExpr::PeekSlice(start, end) => match end { Some(end) => write!(f, "PEEK[{}..{}]", start, end), None => write!(f, "PEEK[{}..]", start), }, OptimizedExpr::PosPred(expr) => write!(f, "&{}", expr.as_ref()), OptimizedExpr::NegPred(expr) => write!(f, "!{}", expr.as_ref()), OptimizedExpr::Seq(lhs, rhs) => { let mut nodes = Vec::new(); nodes.push(lhs); let mut current = rhs; while let OptimizedExpr::Seq(lhs, rhs) = current.as_ref() { nodes.push(lhs); current = rhs; } nodes.push(current); let sequence = nodes .iter() .map(|node| format!("{}", node)) .collect::>() .join(" ~ "); write!(f, "({})", sequence) } OptimizedExpr::Choice(lhs, rhs) => { let mut nodes = Vec::new(); nodes.push(lhs); let mut current = rhs; while let OptimizedExpr::Choice(lhs, rhs) = current.as_ref() { nodes.push(lhs); current = rhs; } nodes.push(current); let sequence = nodes .iter() .map(|node| format!("{}", node)) .collect::>() .join(" | "); write!(f, "({})", sequence) } OptimizedExpr::Opt(expr) => write!(f, "{}?", expr), OptimizedExpr::Rep(expr) => write!(f, "{}*", expr), #[cfg(feature = "grammar-extras")] OptimizedExpr::RepOnce(expr) => write!(f, "{}+", expr), OptimizedExpr::Skip(strings) => { let strings = strings .iter() .map(|s| format!("{:?}", s)) .collect::>() .join(" | "); write!(f, "(!({}) ~ ANY)*", strings) } OptimizedExpr::Push(expr) => write!(f, "PUSH({})", expr), #[cfg(feature = "grammar-extras")] OptimizedExpr::NodeTag(expr, tag) => { write!(f, "(#{} = {})", tag, expr) } OptimizedExpr::RestoreOnErr(expr) => core::fmt::Display::fmt(expr.as_ref(), f), } } } /// A top-down iterator over an `OptimizedExpr`. pub struct OptimizedExprTopDownIterator { current: Option, next: Option, right_branches: Vec, } impl OptimizedExprTopDownIterator { /// Creates a new top down iterator from an `OptimizedExpr`. pub fn new(expr: &OptimizedExpr) -> Self { let mut iter = OptimizedExprTopDownIterator { current: None, next: None, right_branches: vec![], }; iter.iterate_expr(expr.clone()); iter } fn iterate_expr(&mut self, expr: OptimizedExpr) { self.current = Some(expr.clone()); match expr { OptimizedExpr::Seq(lhs, rhs) => { self.right_branches.push(*rhs); self.next = Some(*lhs); } OptimizedExpr::Choice(lhs, rhs) => { self.right_branches.push(*rhs); self.next = Some(*lhs); } OptimizedExpr::PosPred(expr) | OptimizedExpr::NegPred(expr) | OptimizedExpr::Rep(expr) | OptimizedExpr::Opt(expr) | OptimizedExpr::Push(expr) => { self.next = Some(*expr); } _ => { self.next = None; } } } } impl Iterator for OptimizedExprTopDownIterator { type Item = OptimizedExpr; fn next(&mut self) -> Option { let result = self.current.take(); if let Some(expr) = self.next.take() { self.iterate_expr(expr); } else if let Some(expr) = self.right_branches.pop() { self.iterate_expr(expr); } result } } #[cfg(test)] mod tests { use super::*; #[test] fn rotate() { let rules = { use crate::ast::Expr::*; vec![Rule { name: "rule".to_owned(), ty: RuleType::Normal, expr: box_tree!(Choice( Choice( Choice(Str(String::from("a")), Str(String::from("b"))), Str(String::from("c")) ), Str(String::from("d")) )), }] }; let rotated = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Normal, expr: box_tree!(Choice( Str(String::from("a")), Choice( Str(String::from("b")), Choice(Str(String::from("c")), Str(String::from("d"))) ) )), }] }; assert_eq!(optimize(rules), rotated); } #[test] fn skip() { let rules = { use crate::ast::Expr::*; vec![Rule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: box_tree!(Rep(Seq( NegPred(Choice(Str(String::from("a")), Str(String::from("b")))), Ident(String::from("ANY")) ))), }] }; let skipped = vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: OptimizedExpr::Skip(vec![String::from("a"), String::from("b")]), }]; assert_eq!(optimize(rules), skipped); } #[test] fn concat_strings() { let rules = { use crate::ast::Expr::*; vec![Rule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: box_tree!(Seq( Seq(Str(String::from("a")), Str(String::from("b"))), Seq(Str(String::from("c")), Str(String::from("d"))) )), }] }; let concatenated = vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: OptimizedExpr::Str(String::from("abcd")), }]; assert_eq!(optimize(rules), concatenated); } #[test] fn unroll_loop_exact() { let rules = vec![Rule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: Expr::RepExact(Box::new(Expr::Ident(String::from("a"))), 3), }]; let unrolled = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: box_tree!(Seq( Ident(String::from("a")), Seq(Ident(String::from("a")), Ident(String::from("a"))) )), }] }; assert_eq!(optimize(rules), unrolled); } #[test] fn unroll_loop_max() { let rules = vec![Rule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: Expr::RepMax(Box::new(Expr::Str("a".to_owned())), 3), }]; let unrolled = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: box_tree!(Seq( Opt(Str(String::from("a"))), Seq(Opt(Str(String::from("a"))), Opt(Str(String::from("a")))) )), }] }; assert_eq!(optimize(rules), unrolled); } #[test] fn unroll_loop_min() { let rules = vec![Rule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: Expr::RepMin(Box::new(Expr::Str("a".to_owned())), 2), }]; let unrolled = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: box_tree!(Seq( Str(String::from("a")), Seq(Str(String::from("a")), Rep(Str(String::from("a")))) )), }] }; assert_eq!(optimize(rules), unrolled); } #[test] fn unroll_loop_min_max() { let rules = vec![Rule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: Expr::RepMinMax(Box::new(Expr::Str("a".to_owned())), 2, 3), }]; let unrolled = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Atomic, /* TODO possible room for improvement here: * if the sequences were rolled out in the opposite * order, we could further optimize the strings * in cases like this. Str(String::from(("aa")), Opt(Str(String::from("a"))) */ expr: box_tree!(Seq( Str(String::from("a")), Seq(Str(String::from("a")), Opt(Str(String::from("a")))) )), }] }; assert_eq!(optimize(rules), unrolled); } #[test] fn concat_insensitive_strings() { let rules = { use crate::ast::Expr::*; vec![Rule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: box_tree!(Seq( Seq(Insens(String::from("a")), Insens(String::from("b"))), Seq(Insens(String::from("c")), Insens(String::from("d"))) )), }] }; let concatenated = vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: OptimizedExpr::Insens(String::from("abcd")), }]; assert_eq!(optimize(rules), concatenated); } #[test] fn long_common_sequence() { let rules = { use crate::ast::Expr::*; vec![Rule { name: "rule".to_owned(), ty: RuleType::Silent, expr: box_tree!(Choice( Seq( Ident(String::from("a")), Seq(Ident(String::from("b")), Ident(String::from("c"))) ), Seq( Seq(Ident(String::from("a")), Ident(String::from("b"))), Ident(String::from("d")) ) )), }] }; let optimized = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Silent, expr: box_tree!(Seq( Ident(String::from("a")), Seq( Ident(String::from("b")), Choice(Ident(String::from("c")), Ident(String::from("d"))) ) )), }] }; assert_eq!(optimize(rules), optimized); } #[test] fn short_common_sequence() { let rules = { use crate::ast::Expr::*; vec![Rule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: box_tree!(Choice( Seq(Ident(String::from("a")), Ident(String::from("b"))), Ident(String::from("a")) )), }] }; let optimized = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Atomic, expr: box_tree!(Seq(Ident(String::from("a")), Opt(Ident(String::from("b"))))), }] }; assert_eq!(optimize(rules), optimized); } #[test] fn impossible_common_sequence() { let rules = { use crate::ast::Expr::*; vec![Rule { name: "rule".to_owned(), ty: RuleType::Silent, expr: box_tree!(Choice( Ident(String::from("a")), Seq(Ident(String::from("a")), Ident(String::from("b"))) )), }] }; let optimized = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Silent, expr: box_tree!(Ident(String::from("a"))), }] }; assert_eq!(optimize(rules), optimized); } #[test] fn lister() { let rules = { use crate::ast::Expr::*; vec![Rule { name: "rule".to_owned(), ty: RuleType::Silent, expr: box_tree!(Seq( Rep(Seq(Ident(String::from("a")), Ident(String::from("b")))), Ident(String::from("a")) )), }] }; let optimized = { use crate::optimizer::OptimizedExpr::*; vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Silent, expr: box_tree!(Seq( Ident(String::from("a")), Rep(Seq(Ident(String::from("b")), Ident(String::from("a")))) )), }] }; assert_eq!(optimize(rules), optimized); } mod display { use super::super::*; /// In previous implementation of Display for OptimizedExpr /// in commit 48e0a8bd3d43a17c1c78f099610b745d18ec0c5f (actually committed by me), /// Str("\n") will be displayed as /// " /// " /// /// It will not break the compilation in normal use. /// /// But when I use it in automatically generating documents, /// it will quite confusing and we'll be unable to distinguish \n and \r. /// /// And `cargo expand` will emit codes that can't be compiled, /// for it expand `#[doc("...")]` to `/// ...`, /// and when the document comment breaks the line, /// it will be expanded into wrong codes. #[test] fn control_character() { assert_eq!(OptimizedExpr::Str("\n".to_owned()).to_string(), "\"\\n\""); assert_eq!( OptimizedExpr::Insens("\n".to_owned()).to_string(), "^\"\\n\"", ); assert_eq!( OptimizedExpr::Range("\n".to_owned(), "\r".to_owned()).to_string(), "('\\n'..'\\r')", ); assert_eq!( OptimizedExpr::Skip(vec![ "\n".to_owned(), "\r".to_owned(), "\n\r".to_owned(), "\0".to_owned(), ]) .to_string(), r#"(!("\n" | "\r" | "\n\r" | "\0") ~ ANY)*"#, ); assert_ne!(OptimizedExpr::Str("\n".to_owned()).to_string(), "\"\n\""); } #[test] fn str() { assert_eq!(OptimizedExpr::Str("a".to_owned()).to_string(), r#""a""#); } #[test] fn insens() { assert_eq!(OptimizedExpr::Insens("a".to_owned()).to_string(), r#"^"a""#); } #[test] fn range() { assert_eq!( OptimizedExpr::Range("a".to_owned(), "z".to_owned()).to_string(), r#"('a'..'z')"#, ); } #[test] fn ident() { assert_eq!(OptimizedExpr::Ident("a".to_owned()).to_string(), r#"a"#); } #[test] fn peek_slice() { assert_eq!(OptimizedExpr::PeekSlice(0, None).to_string(), "PEEK[0..]"); assert_eq!( OptimizedExpr::PeekSlice(0, Some(-1)).to_string(), "PEEK[0..-1]", ); assert_eq!( OptimizedExpr::PeekSlice(2, Some(3)).to_string(), "PEEK[2..3]", ); assert_eq!( OptimizedExpr::PeekSlice(2, Some(-1)).to_string(), "PEEK[2..-1]", ); assert_eq!(OptimizedExpr::PeekSlice(0, None).to_string(), "PEEK[0..]"); } #[test] fn pos_pred() { assert_eq!( OptimizedExpr::PosPred(Box::new(OptimizedExpr::NegPred(Box::new( OptimizedExpr::Ident("a".to_owned()), )))) .to_string(), "&!a", ); assert_eq!( OptimizedExpr::PosPred(Box::new(OptimizedExpr::Choice( Box::new(OptimizedExpr::Rep(Box::new(OptimizedExpr::Ident( "a".to_owned(), )))), Box::new(OptimizedExpr::Str("a".to_owned())), ))) .to_string(), r#"&(a* | "a")"#, ); assert_eq!( OptimizedExpr::PosPred(Box::new(OptimizedExpr::RestoreOnErr(Box::new( OptimizedExpr::NegPred(Box::new(OptimizedExpr::Ident("a".to_owned()))), )))) .to_string(), "&!a", ); } #[test] fn neg_pred() { assert_eq!( OptimizedExpr::NegPred(Box::new(OptimizedExpr::Ident("e".to_owned()))).to_string(), r#"!e"#, ); assert_eq!( OptimizedExpr::NegPred(Box::new(OptimizedExpr::Choice( Box::new(OptimizedExpr::Push(Box::new(OptimizedExpr::Ident( "a".to_owned(), )))), Box::new(OptimizedExpr::Str("a".to_owned())), ))) .to_string(), r#"!(PUSH(a) | "a")"#, ); } #[test] fn seq() { assert_eq!( OptimizedExpr::Seq( Box::new(OptimizedExpr::Ident("e1".to_owned())), Box::new(OptimizedExpr::Ident("e2".to_owned())), ) .to_string(), r#"(e1 ~ e2)"#, ); assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Ident("e3".to_owned())), )), ) .to_string(), "(e1 ~ e2 ~ e3)", ); assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 ~ e2 ~ e3 ~ e4)", ); assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 ~ (e2 | (e3 ~ e4)))", ); assert_eq!( Expr::Seq( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 ~ e2 ~ (e3 | e4))", ); assert_eq!( OptimizedExpr::Seq( Box::new(OptimizedExpr::Rep(Box::new(OptimizedExpr::Str( "a".to_owned(), )))), Box::new(OptimizedExpr::Seq( Box::new(OptimizedExpr::Ident("b".to_owned())), Box::new(OptimizedExpr::Insens("c".to_owned())), )), ) .to_string(), r#"("a"* ~ b ~ ^"c")"#, ); assert_eq!( OptimizedExpr::Seq( Box::new(OptimizedExpr::PosPred(Box::new(OptimizedExpr::Range( "a".to_owned(), "z".to_owned(), )))), Box::new(OptimizedExpr::NegPred(Box::new(OptimizedExpr::Opt( Box::new(OptimizedExpr::Range("A".to_owned(), "Z".to_owned())), )))), ) .to_string(), "(&('a'..'z') ~ !('A'..'Z')?)", ); } #[test] fn choice() { assert_eq!( OptimizedExpr::Choice( Box::new(OptimizedExpr::Ident("e1".to_owned())), Box::new(OptimizedExpr::Ident("e2".to_owned())), ) .to_string(), r#"(e1 | e2)"#, ); assert_eq!( Expr::Choice( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Ident("e3".to_owned())), )), ) .to_string(), "(e1 | e2 | e3)", ); assert_eq!( Expr::Choice( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 | e2 | e3 | e4)", ); assert_eq!( Expr::Choice( Box::new(Expr::Ident("e1".to_owned())), Box::new(Expr::Seq( Box::new(Expr::Ident("e2".to_owned())), Box::new(Expr::Choice( Box::new(Expr::Ident("e3".to_owned())), Box::new(Expr::Ident("e4".to_owned())), )), )), ) .to_string(), "(e1 | (e2 ~ (e3 | e4)))", ); assert_eq!( OptimizedExpr::Choice( Box::new(OptimizedExpr::Str("a".to_owned())), Box::new(OptimizedExpr::Choice( Box::new(OptimizedExpr::Push(Box::new(OptimizedExpr::Ident( "b".to_owned(), )))), Box::new(OptimizedExpr::Insens("c".to_owned())), )), ) .to_string(), r#"("a" | PUSH(b) | ^"c")"#, ); } #[test] fn opt() { assert_eq!( OptimizedExpr::Opt(Box::new(OptimizedExpr::Ident("e".to_owned()))).to_string(), "e?", ); } #[test] fn rep() { assert_eq!( OptimizedExpr::Rep(Box::new(OptimizedExpr::Ident("x".to_owned()))).to_string(), "x*", ); assert_eq!( OptimizedExpr::Rep(Box::new(OptimizedExpr::Range( "0".to_owned(), "9".to_owned(), ))) .to_string(), "('0'..'9')*", ); } #[test] #[cfg(feature = "grammar-extras")] fn rep_once() { assert_eq!( OptimizedExpr::RepOnce(Box::new(OptimizedExpr::Ident("e".to_owned()))).to_string(), "e+", ); assert_eq!( OptimizedExpr::RepOnce(Box::new(OptimizedExpr::Range( "0".to_owned(), "9".to_owned(), ))) .to_string(), "('0'..'9')+", ); } #[test] fn skip() { assert_eq!( OptimizedExpr::Skip( ["a", "bc"] .into_iter() .map(|s| s.to_owned()) .collect::>(), ) .to_string(), r#"(!("a" | "bc") ~ ANY)*"#, ); } #[test] fn push() { assert_eq!( OptimizedExpr::Push(Box::new(OptimizedExpr::Ident("e".to_owned()))).to_string(), "PUSH(e)", ); } #[test] #[cfg(feature = "grammar-extras")] fn node_tag() { assert_eq!( OptimizedExpr::NodeTag( Box::new(OptimizedExpr::Ident("expr".to_owned())), "label".to_owned(), ) .to_string(), r#"(#label = expr)"#, ); assert_eq!( OptimizedExpr::NodeTag( Box::new(OptimizedExpr::Ident("x".to_owned())), "X".to_owned(), ) .to_string(), r#"(#X = x)"#, ); assert_eq!( OptimizedExpr::NodeTag( Box::new(OptimizedExpr::Seq( Box::new(OptimizedExpr::Ident("x".to_owned())), Box::new(OptimizedExpr::Str("y".to_owned())), )), "X".to_owned(), ) .to_string(), r#"(#X = (x ~ "y"))"#, ); } #[test] fn restore_on_err() { assert_eq!( OptimizedExpr::RestoreOnErr(Box::new(OptimizedExpr::Ident("e".to_owned()))) .to_string(), "e", ); } } } pest_meta-2.7.4/src/optimizer/restorer.rs000064400000000000000000000112261046102023000166450ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. use std::collections::HashMap; use crate::optimizer::*; pub fn restore_on_err( rule: OptimizedRule, rules: &HashMap, ) -> OptimizedRule { let OptimizedRule { name, ty, expr } = rule; let expr = expr.map_bottom_up(|expr| wrap_branching_exprs(expr, rules)); OptimizedRule { name, ty, expr } } fn wrap_branching_exprs( expr: OptimizedExpr, rules: &HashMap, ) -> OptimizedExpr { match expr { OptimizedExpr::Opt(expr) => { if child_modifies_state(&expr, rules, &mut HashMap::new()) { OptimizedExpr::Opt(Box::new(OptimizedExpr::RestoreOnErr(expr))) } else { OptimizedExpr::Opt(expr) } } OptimizedExpr::Choice(lhs, rhs) => { let wrapped_lhs = if child_modifies_state(&lhs, rules, &mut HashMap::new()) { Box::new(OptimizedExpr::RestoreOnErr(lhs)) } else { lhs }; let wrapped_rhs = if child_modifies_state(&rhs, rules, &mut HashMap::new()) { Box::new(OptimizedExpr::RestoreOnErr(rhs)) } else { rhs }; OptimizedExpr::Choice(wrapped_lhs, wrapped_rhs) } OptimizedExpr::Rep(expr) => { if child_modifies_state(&expr, rules, &mut HashMap::new()) { OptimizedExpr::Rep(Box::new(OptimizedExpr::RestoreOnErr(expr))) } else { OptimizedExpr::Rep(expr) } } _ => expr, } } fn child_modifies_state( expr: &OptimizedExpr, rules: &HashMap, cache: &mut HashMap>, ) -> bool { expr.iter_top_down().any(|expr| match expr { OptimizedExpr::Push(_) => true, OptimizedExpr::Ident(ref name) if name == "DROP" => true, OptimizedExpr::Ident(ref name) if name == "POP" => true, OptimizedExpr::Ident(ref name) => match cache.get(name).cloned() { Some(option) => match option { Some(cached) => cached, None => { cache.insert(name.to_owned(), Some(false)); false } }, None => { cache.insert(name.to_owned(), None); let result = match rules.get(name) { Some(expr) => child_modifies_state(expr, rules, cache), None => false, }; cache.insert(name.to_owned(), Some(result)); result } }, _ => false, }) } #[cfg(test)] mod tests { use super::*; use crate::optimizer::OptimizedExpr::*; #[test] fn restore_no_stack_children() { let rules = vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Normal, expr: box_tree!(Opt(Str("a".to_string()))), }]; assert_eq!( restore_on_err(rules[0].clone(), &to_hash_map(&rules)), rules[0].clone() ); } #[test] fn restore_with_child_stack_ops() { let rules = vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Normal, expr: box_tree!(Rep(Push(Str("a".to_string())))), }]; let restored = OptimizedRule { name: "rule".to_owned(), ty: RuleType::Normal, expr: box_tree!(Rep(RestoreOnErr(Push(Str("a".to_string()))))), }; assert_eq!( restore_on_err(rules[0].clone(), &to_hash_map(&rules)), restored ); } #[test] fn restore_choice_branch_with_and_branch_without() { let rules = vec![OptimizedRule { name: "rule".to_owned(), ty: RuleType::Normal, expr: box_tree!(Choice(Push(Str("a".to_string())), Str("a".to_string()))), }]; let restored = OptimizedRule { name: "rule".to_owned(), ty: RuleType::Normal, expr: box_tree!(Choice( RestoreOnErr(Push(Str("a".to_string()))), Str("a".to_string()) )), }; assert_eq!( restore_on_err(rules[0].clone(), &to_hash_map(&rules)), restored ); } } pest_meta-2.7.4/src/optimizer/rotater.rs000064400000000000000000000025641046102023000164650ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. use crate::ast::*; pub fn rotate(rule: Rule) -> Rule { fn rotate_internal(expr: Expr) -> Expr { match expr { Expr::Seq(lhs, rhs) => { let lhs = *lhs; match lhs { Expr::Seq(ll, lr) => { rotate_internal(Expr::Seq(ll, Box::new(Expr::Seq(lr, rhs)))) } lhs => Expr::Seq(Box::new(lhs), rhs), } } Expr::Choice(lhs, rhs) => { let lhs = *lhs; match lhs { Expr::Choice(ll, lr) => { rotate_internal(Expr::Choice(ll, Box::new(Expr::Choice(lr, rhs)))) } lhs => Expr::Choice(Box::new(lhs), rhs), } } expr => expr, } } let Rule { name, ty, expr } = rule; Rule { name, ty, expr: expr.map_top_down(rotate_internal), } } pest_meta-2.7.4/src/optimizer/skipper.rs000064400000000000000000000033421046102023000164550ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. use crate::ast::*; pub fn skip(rule: Rule) -> Rule { fn populate_choices(expr: Expr, mut choices: Vec) -> Option { match expr { Expr::Choice(lhs, rhs) => { if let Expr::Str(string) = *lhs { choices.push(string); populate_choices(*rhs, choices) } else { None } } Expr::Str(string) => { choices.push(string); Some(Expr::Skip(choices)) } _ => None, } } let Rule { name, ty, expr } = rule; Rule { name, ty, expr: if ty == RuleType::Atomic { expr.map_top_down(|expr| { if let Expr::Rep(expr) = expr.clone() { if let Expr::Seq(lhs, rhs) = *expr { if let (Expr::NegPred(expr), Expr::Ident(ident)) = (*lhs, *rhs) { if ident == "ANY" { if let Some(expr) = populate_choices(*expr, vec![]) { return expr; } } } } }; expr }) } else { expr }, } } pest_meta-2.7.4/src/optimizer/unroller.rs000064400000000000000000000046701046102023000166470ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. use crate::ast::*; pub fn unroll(rule: Rule) -> Rule { let Rule { name, ty, expr } = rule; Rule { name, ty, expr: expr.map_bottom_up(|expr| match expr { #[cfg(not(feature = "grammar-extras"))] Expr::RepOnce(expr) => Expr::Seq(expr.clone(), Box::new(Expr::Rep(expr))), Expr::RepExact(expr, num) => (1..num + 1) .map(|_| *expr.clone()) .rev() .fold(None, |rep, expr| match rep { None => Some(expr), Some(rep) => Some(Expr::Seq(Box::new(expr), Box::new(rep))), }) .unwrap(), Expr::RepMin(expr, min) => (1..min + 2) .map(|i| { if i <= min { *expr.clone() } else { Expr::Rep(expr.clone()) } }) .rev() .fold(None, |rep, expr| match rep { None => Some(expr), Some(rep) => Some(Expr::Seq(Box::new(expr), Box::new(rep))), }) .unwrap(), Expr::RepMax(expr, max) => (1..max + 1) .map(|_| Expr::Opt(expr.clone())) .rev() .fold(None, |rep, expr| match rep { None => Some(expr), Some(rep) => Some(Expr::Seq(Box::new(expr), Box::new(rep))), }) .unwrap(), Expr::RepMinMax(expr, min, max) => (1..max + 1) .map(|i| { if i <= min { *expr.clone() } else { Expr::Opt(expr.clone()) } }) .rev() .fold(None, |rep, expr| match rep { None => Some(expr), Some(rep) => Some(Expr::Seq(Box::new(expr), Box::new(rep))), }) .unwrap(), expr => expr, }), } } pest_meta-2.7.4/src/parser.rs000064400000000000000000001607761046102023000142710ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. //! Types and helpers for the pest's own grammar parser. use std::char; use std::iter::Peekable; use pest::error::{Error, ErrorVariant}; use pest::iterators::{Pair, Pairs}; use pest::pratt_parser::{Assoc, Op, PrattParser}; use pest::{Parser, Position, Span}; use crate::ast::{Expr, Rule as AstRule, RuleType}; use crate::validator; #[allow(missing_docs, unused_qualifications)] mod grammar { #[cfg(not(feature = "not-bootstrap-in-src"))] include!("grammar.rs"); #[cfg(feature = "not-bootstrap-in-src")] include!(concat!(env!("OUT_DIR"), "/__pest_grammar.rs")); } pub use self::grammar::*; /// A helper that will parse using the pest grammar #[allow(clippy::perf)] pub fn parse(rule: Rule, data: &str) -> Result, Error> { PestParser::parse(rule, data) } /// The pest grammar rule #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParserRule<'i> { /// The rule's name pub name: String, /// The rule's span pub span: Span<'i>, /// The rule's type pub ty: RuleType, /// The rule's parser node pub node: ParserNode<'i>, } /// The pest grammar node #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParserNode<'i> { /// The node's expression pub expr: ParserExpr<'i>, /// The node's span pub span: Span<'i>, } impl<'i> ParserNode<'i> { /// will remove nodes that do not match `f` pub fn filter_map_top_down(self, mut f: F) -> Vec where F: FnMut(ParserNode<'i>) -> Option, { pub fn filter_internal<'i, F, T>(node: ParserNode<'i>, f: &mut F, result: &mut Vec) where F: FnMut(ParserNode<'i>) -> Option, { if let Some(value) = f(node.clone()) { result.push(value); } match node.expr { ParserExpr::PosPred(node) => { filter_internal(*node, f, result); } ParserExpr::NegPred(node) => { filter_internal(*node, f, result); } ParserExpr::Seq(lhs, rhs) => { filter_internal(*lhs, f, result); filter_internal(*rhs, f, result); } ParserExpr::Choice(lhs, rhs) => { filter_internal(*lhs, f, result); filter_internal(*rhs, f, result); } ParserExpr::Rep(node) => { filter_internal(*node, f, result); } ParserExpr::RepOnce(node) => { filter_internal(*node, f, result); } ParserExpr::RepExact(node, _) => { filter_internal(*node, f, result); } ParserExpr::RepMin(node, _) => { filter_internal(*node, f, result); } ParserExpr::RepMax(node, _) => { filter_internal(*node, f, result); } ParserExpr::RepMinMax(node, ..) => { filter_internal(*node, f, result); } ParserExpr::Opt(node) => { filter_internal(*node, f, result); } ParserExpr::Push(node) => { filter_internal(*node, f, result); } _ => (), } } let mut result = vec![]; filter_internal(self, &mut f, &mut result); result } } /// All possible parser expressions #[derive(Clone, Debug, Eq, PartialEq)] pub enum ParserExpr<'i> { /// Matches an exact string, e.g. `"a"` Str(String), /// Matches an exact string, case insensitively (ASCII only), e.g. `^"a"` Insens(String), /// Matches one character in the range, e.g. `'a'..'z'` Range(String, String), /// Matches the rule with the given name, e.g. `a` Ident(String), /// Matches a custom part of the stack, e.g. `PEEK[..]` PeekSlice(i32, Option), /// Positive lookahead; matches expression without making progress, e.g. `&e` PosPred(Box>), /// Negative lookahead; matches if expression doesn't match, without making progress, e.g. `!e` NegPred(Box>), /// Matches a sequence of two expressions, e.g. `e1 ~ e2` Seq(Box>, Box>), /// Matches either of two expressions, e.g. `e1 | e2` Choice(Box>, Box>), /// Optionally matches an expression, e.g. `e?` Opt(Box>), /// Matches an expression zero or more times, e.g. `e*` Rep(Box>), /// Matches an expression one or more times, e.g. `e+` RepOnce(Box>), /// Matches an expression an exact number of times, e.g. `e{n}` RepExact(Box>, u32), /// Matches an expression at least a number of times, e.g. `e{n,}` RepMin(Box>, u32), /// Matches an expression at most a number of times, e.g. `e{,n}` RepMax(Box>, u32), /// Matches an expression a number of times within a range, e.g. `e{m, n}` RepMinMax(Box>, u32, u32), /// Matches an expression and pushes it to the stack, e.g. `push(e)` Push(Box>), /// Matches an expression and assigns a label to it, e.g. #label = exp #[cfg(feature = "grammar-extras")] NodeTag(Box>, String), } fn convert_rule(rule: ParserRule<'_>) -> AstRule { let ParserRule { name, ty, node, .. } = rule; let expr = convert_node(node); AstRule { name, ty, expr } } fn convert_node(node: ParserNode<'_>) -> Expr { match node.expr { ParserExpr::Str(string) => Expr::Str(string), ParserExpr::Insens(string) => Expr::Insens(string), ParserExpr::Range(start, end) => Expr::Range(start, end), ParserExpr::Ident(ident) => Expr::Ident(ident), ParserExpr::PeekSlice(start, end) => Expr::PeekSlice(start, end), ParserExpr::PosPred(node) => Expr::PosPred(Box::new(convert_node(*node))), ParserExpr::NegPred(node) => Expr::NegPred(Box::new(convert_node(*node))), ParserExpr::Seq(node1, node2) => Expr::Seq( Box::new(convert_node(*node1)), Box::new(convert_node(*node2)), ), ParserExpr::Choice(node1, node2) => Expr::Choice( Box::new(convert_node(*node1)), Box::new(convert_node(*node2)), ), ParserExpr::Opt(node) => Expr::Opt(Box::new(convert_node(*node))), ParserExpr::Rep(node) => Expr::Rep(Box::new(convert_node(*node))), ParserExpr::RepOnce(node) => Expr::RepOnce(Box::new(convert_node(*node))), ParserExpr::RepExact(node, num) => Expr::RepExact(Box::new(convert_node(*node)), num), ParserExpr::RepMin(node, max) => Expr::RepMin(Box::new(convert_node(*node)), max), ParserExpr::RepMax(node, max) => Expr::RepMax(Box::new(convert_node(*node)), max), ParserExpr::RepMinMax(node, min, max) => { Expr::RepMinMax(Box::new(convert_node(*node)), min, max) } ParserExpr::Push(node) => Expr::Push(Box::new(convert_node(*node))), #[cfg(feature = "grammar-extras")] ParserExpr::NodeTag(node, tag) => Expr::NodeTag(Box::new(convert_node(*node)), tag), } } /// Converts a parser's result (`Pairs`) to an AST pub fn consume_rules(pairs: Pairs<'_, Rule>) -> Result, Vec>> { let rules = consume_rules_with_spans(pairs)?; let errors = validator::validate_ast(&rules); if errors.is_empty() { Ok(rules.into_iter().map(convert_rule).collect()) } else { Err(errors) } } /// A helper function to rename verbose rules /// for the sake of better error messages #[inline] pub fn rename_meta_rule(rule: &Rule) -> String { match *rule { Rule::grammar_rule => "rule".to_owned(), Rule::_push => "PUSH".to_owned(), Rule::assignment_operator => "`=`".to_owned(), Rule::silent_modifier => "`_`".to_owned(), Rule::atomic_modifier => "`@`".to_owned(), Rule::compound_atomic_modifier => "`$`".to_owned(), Rule::non_atomic_modifier => "`!`".to_owned(), Rule::opening_brace => "`{`".to_owned(), Rule::closing_brace => "`}`".to_owned(), Rule::opening_brack => "`[`".to_owned(), Rule::closing_brack => "`]`".to_owned(), Rule::opening_paren => "`(`".to_owned(), Rule::positive_predicate_operator => "`&`".to_owned(), Rule::negative_predicate_operator => "`!`".to_owned(), Rule::sequence_operator => "`&`".to_owned(), Rule::choice_operator => "`|`".to_owned(), Rule::optional_operator => "`?`".to_owned(), Rule::repeat_operator => "`*`".to_owned(), Rule::repeat_once_operator => "`+`".to_owned(), Rule::comma => "`,`".to_owned(), Rule::closing_paren => "`)`".to_owned(), Rule::quote => "`\"`".to_owned(), Rule::insensitive_string => "`^`".to_owned(), Rule::range_operator => "`..`".to_owned(), Rule::single_quote => "`'`".to_owned(), Rule::grammar_doc => "//!".to_owned(), Rule::line_doc => "///".to_owned(), other_rule => format!("{:?}", other_rule), } } fn consume_rules_with_spans( pairs: Pairs<'_, Rule>, ) -> Result>, Vec>> { let pratt = PrattParser::new() .op(Op::infix(Rule::choice_operator, Assoc::Left)) .op(Op::infix(Rule::sequence_operator, Assoc::Left)); pairs .filter(|pair| pair.as_rule() == Rule::grammar_rule) .filter(|pair| { // To ignore `grammar_rule > line_doc` pairs let mut pairs = pair.clone().into_inner(); let pair = pairs.next().unwrap(); pair.as_rule() != Rule::line_doc }) .map(|pair| { let mut pairs = pair.into_inner().peekable(); let span = pairs.next().unwrap().as_span(); let name = span.as_str().to_owned(); pairs.next().unwrap(); // assignment_operator let ty = if pairs.peek().unwrap().as_rule() != Rule::opening_brace { match pairs.next().unwrap().as_rule() { Rule::silent_modifier => RuleType::Silent, Rule::atomic_modifier => RuleType::Atomic, Rule::compound_atomic_modifier => RuleType::CompoundAtomic, Rule::non_atomic_modifier => RuleType::NonAtomic, _ => unreachable!(), } } else { RuleType::Normal }; pairs.next().unwrap(); // opening_brace // skip initial infix operators let mut inner_nodes = pairs.next().unwrap().into_inner().peekable(); if inner_nodes.peek().unwrap().as_rule() == Rule::choice_operator { inner_nodes.next().unwrap(); } let node = consume_expr(inner_nodes, &pratt)?; Ok(ParserRule { name, span, ty, node, }) }) .collect() } fn get_node_tag<'i>( pairs: &mut Peekable>, ) -> (Pair<'i, Rule>, Option<(String, Position<'i>)>) { let pair_or_tag = pairs.next().unwrap(); if let Some(next_pair) = pairs.peek() { if next_pair.as_rule() == Rule::assignment_operator { pairs.next().unwrap(); let pair = pairs.next().unwrap(); ( pair, Some(( pair_or_tag.as_str()[1..].to_string(), pair_or_tag.as_span().start_pos(), )), ) } else { (pair_or_tag, None) } } else { (pair_or_tag, None) } } fn consume_expr<'i>( pairs: Peekable>, pratt: &PrattParser, ) -> Result, Vec>> { fn unaries<'i>( mut pairs: Peekable>, pratt: &PrattParser, ) -> Result, Vec>> { #[cfg(feature = "grammar-extras")] let (pair, tag_start) = get_node_tag(&mut pairs); #[cfg(not(feature = "grammar-extras"))] let (pair, _tag_start) = get_node_tag(&mut pairs); let node = match pair.as_rule() { Rule::opening_paren => { let node = unaries(pairs, pratt)?; let end = node.span.end_pos(); ParserNode { expr: node.expr, span: pair.as_span().start_pos().span(&end), } } Rule::positive_predicate_operator => { let node = unaries(pairs, pratt)?; let end = node.span.end_pos(); ParserNode { expr: ParserExpr::PosPred(Box::new(node)), span: pair.as_span().start_pos().span(&end), } } Rule::negative_predicate_operator => { let node = unaries(pairs, pratt)?; let end = node.span.end_pos(); ParserNode { expr: ParserExpr::NegPred(Box::new(node)), span: pair.as_span().start_pos().span(&end), } } other_rule => { let node = match other_rule { Rule::expression => consume_expr(pair.into_inner().peekable(), pratt)?, Rule::_push => { let start = pair.clone().as_span().start_pos(); let mut pairs = pair.into_inner(); pairs.next().unwrap(); // opening_paren let pair = pairs.next().unwrap(); let node = consume_expr(pair.into_inner().peekable(), pratt)?; let end = node.span.end_pos(); ParserNode { expr: ParserExpr::Push(Box::new(node)), span: start.span(&end), } } Rule::peek_slice => { let mut pairs = pair.clone().into_inner(); pairs.next().unwrap(); // opening_brack let pair_start = pairs.next().unwrap(); // .. or integer let start: i32 = match pair_start.as_rule() { Rule::range_operator => 0, Rule::integer => { pairs.next().unwrap(); // .. pair_start.as_str().parse().unwrap() } _ => unreachable!("peek start"), }; let pair_end = pairs.next().unwrap(); // integer or } let end: Option = match pair_end.as_rule() { Rule::closing_brack => None, Rule::integer => { pairs.next().unwrap(); // } Some(pair_end.as_str().parse().unwrap()) } _ => unreachable!("peek end"), }; ParserNode { expr: ParserExpr::PeekSlice(start, end), span: pair.as_span(), } } Rule::identifier => ParserNode { expr: ParserExpr::Ident(pair.as_str().to_owned()), span: pair.clone().as_span(), }, Rule::string => { let string = unescape(pair.as_str()).expect("incorrect string literal"); ParserNode { expr: ParserExpr::Str(string[1..string.len() - 1].to_owned()), span: pair.clone().as_span(), } } Rule::insensitive_string => { let string = unescape(pair.as_str()).expect("incorrect string literal"); ParserNode { expr: ParserExpr::Insens(string[2..string.len() - 1].to_owned()), span: pair.clone().as_span(), } } Rule::range => { let mut pairs = pair.into_inner(); let pair = pairs.next().unwrap(); let start = unescape(pair.as_str()).expect("incorrect char literal"); let start_pos = pair.clone().as_span().start_pos(); pairs.next(); let pair = pairs.next().unwrap(); let end = unescape(pair.as_str()).expect("incorrect char literal"); let end_pos = pair.clone().as_span().end_pos(); ParserNode { expr: ParserExpr::Range( start[1..start.len() - 1].to_owned(), end[1..end.len() - 1].to_owned(), ), span: start_pos.span(&end_pos), } } x => unreachable!("other rule: {:?}", x), }; pairs.try_fold(node, |node: ParserNode<'i>, pair: Pair<'i, Rule>| { let node = match pair.as_rule() { Rule::optional_operator => { let start = node.span.start_pos(); ParserNode { expr: ParserExpr::Opt(Box::new(node)), span: start.span(&pair.as_span().end_pos()), } } Rule::repeat_operator => { let start = node.span.start_pos(); ParserNode { expr: ParserExpr::Rep(Box::new(node)), span: start.span(&pair.as_span().end_pos()), } } Rule::repeat_once_operator => { let start = node.span.start_pos(); ParserNode { expr: ParserExpr::RepOnce(Box::new(node)), span: start.span(&pair.as_span().end_pos()), } } Rule::repeat_exact => { let mut inner = pair.clone().into_inner(); inner.next().unwrap(); // opening_brace let number = inner.next().unwrap(); let num = if let Ok(num) = number.as_str().parse::() { num } else { return Err(vec![Error::new_from_span( ErrorVariant::CustomError { message: "number cannot overflow u32".to_owned(), }, number.as_span(), )]); }; if num == 0 { let error: Error = Error::new_from_span( ErrorVariant::CustomError { message: "cannot repeat 0 times".to_owned(), }, number.as_span(), ); return Err(vec![error]); } let start = node.span.start_pos(); ParserNode { expr: ParserExpr::RepExact(Box::new(node), num), span: start.span(&pair.as_span().end_pos()), } } Rule::repeat_min => { let mut inner = pair.clone().into_inner(); inner.next().unwrap(); // opening_brace let min_number = inner.next().unwrap(); let min = if let Ok(min) = min_number.as_str().parse::() { min } else { return Err(vec![Error::new_from_span( ErrorVariant::CustomError { message: "number cannot overflow u32".to_owned(), }, min_number.as_span(), )]); }; let start = node.span.start_pos(); ParserNode { expr: ParserExpr::RepMin(Box::new(node), min), span: start.span(&pair.as_span().end_pos()), } } Rule::repeat_max => { let mut inner = pair.clone().into_inner(); inner.next().unwrap(); // opening_brace inner.next().unwrap(); // comma let max_number = inner.next().unwrap(); let max = if let Ok(max) = max_number.as_str().parse::() { max } else { return Err(vec![Error::new_from_span( ErrorVariant::CustomError { message: "number cannot overflow u32".to_owned(), }, max_number.as_span(), )]); }; if max == 0 { let error: Error = Error::new_from_span( ErrorVariant::CustomError { message: "cannot repeat 0 times".to_owned(), }, max_number.as_span(), ); return Err(vec![error]); } let start = node.span.start_pos(); ParserNode { expr: ParserExpr::RepMax(Box::new(node), max), span: start.span(&pair.as_span().end_pos()), } } Rule::repeat_min_max => { let mut inner = pair.clone().into_inner(); inner.next().unwrap(); // opening_brace let min_number = inner.next().unwrap(); let min = if let Ok(min) = min_number.as_str().parse::() { min } else { return Err(vec![Error::new_from_span( ErrorVariant::CustomError { message: "number cannot overflow u32".to_owned(), }, min_number.as_span(), )]); }; inner.next().unwrap(); // comma let max_number = inner.next().unwrap(); let max = if let Ok(max) = max_number.as_str().parse::() { max } else { return Err(vec![Error::new_from_span( ErrorVariant::CustomError { message: "number cannot overflow u32".to_owned(), }, max_number.as_span(), )]); }; if max == 0 { let error: Error = Error::new_from_span( ErrorVariant::CustomError { message: "cannot repeat 0 times".to_owned(), }, max_number.as_span(), ); return Err(vec![error]); } let start = node.span.start_pos(); ParserNode { expr: ParserExpr::RepMinMax(Box::new(node), min, max), span: start.span(&pair.as_span().end_pos()), } } Rule::closing_paren => { let start = node.span.start_pos(); ParserNode { expr: node.expr, span: start.span(&pair.as_span().end_pos()), } } rule => unreachable!("node: {:?}", rule), }; Ok(node) })? } }; #[cfg(feature = "grammar-extras")] if let Some((tag, start)) = tag_start { let span = start.span(&node.span.end_pos()); Ok(ParserNode { expr: ParserExpr::NodeTag(Box::new(node), tag), span, }) } else { Ok(node) } #[cfg(not(feature = "grammar-extras"))] Ok(node) } let term = |pair: Pair<'i, Rule>| unaries(pair.into_inner().peekable(), pratt); let infix = |lhs: Result, Vec>>, op: Pair<'i, Rule>, rhs: Result, Vec>>| match op.as_rule() { Rule::sequence_operator => { let lhs = lhs?; let rhs = rhs?; let start = lhs.span.start_pos(); let end = rhs.span.end_pos(); Ok(ParserNode { expr: ParserExpr::Seq(Box::new(lhs), Box::new(rhs)), span: start.span(&end), }) } Rule::choice_operator => { let lhs = lhs?; let rhs = rhs?; let start = lhs.span.start_pos(); let end = rhs.span.end_pos(); Ok(ParserNode { expr: ParserExpr::Choice(Box::new(lhs), Box::new(rhs)), span: start.span(&end), }) } _ => unreachable!("infix"), }; pratt.map_primary(term).map_infix(infix).parse(pairs) } fn unescape(string: &str) -> Option { let mut result = String::new(); let mut chars = string.chars(); loop { match chars.next() { Some('\\') => match chars.next()? { '"' => result.push('"'), '\\' => result.push('\\'), 'r' => result.push('\r'), 'n' => result.push('\n'), 't' => result.push('\t'), '0' => result.push('\0'), '\'' => result.push('\''), 'x' => { let string: String = chars.clone().take(2).collect(); if string.len() != 2 { return None; } for _ in 0..string.len() { chars.next()?; } let value = u8::from_str_radix(&string, 16).ok()?; result.push(char::from(value)); } 'u' => { if chars.next()? != '{' { return None; } let string: String = chars.clone().take_while(|c| *c != '}').collect(); if string.len() < 2 || 6 < string.len() { return None; } for _ in 0..string.len() + 1 { chars.next()?; } let value = u32::from_str_radix(&string, 16).ok()?; result.push(char::from_u32(value)?); } _ => return None, }, Some(c) => result.push(c), None => return Some(result), }; } } #[cfg(test)] mod tests { use std::convert::TryInto; use super::super::unwrap_or_report; use super::*; #[test] fn rules() { parses_to! { parser: PestParser, input: "a = { b } c = { d }", rule: Rule::grammar_rules, tokens: [ grammar_rule(0, 9, [ identifier(0, 1), assignment_operator(2, 3), opening_brace(4, 5), expression(6, 8, [ term(6, 8, [ identifier(6, 7) ]) ]), closing_brace(8, 9) ]), grammar_rule(10, 19, [ identifier(10, 11), assignment_operator(12, 13), opening_brace(14, 15), expression(16, 18, [ term(16, 18, [ identifier(16, 17) ]) ]), closing_brace(18, 19) ]) ] }; } #[test] fn rule() { parses_to! { parser: PestParser, input: "a = ! { b ~ c }", rule: Rule::grammar_rule, tokens: [ grammar_rule(0, 15, [ identifier(0, 1), assignment_operator(2, 3), non_atomic_modifier(4, 5), opening_brace(6, 7), expression(8, 14, [ term(8, 10, [ identifier(8, 9) ]), sequence_operator(10, 11), term(12, 14, [ identifier(12, 13) ]) ]), closing_brace(14, 15) ]) ] }; } #[test] fn expression() { parses_to! { parser: PestParser, input: "_a | 'a'..'b' ~ !^\"abc\" ~ (d | e)*?", rule: Rule::expression, tokens: [ expression(0, 35, [ term(0, 3, [ identifier(0, 2) ]), choice_operator(3, 4), term(5, 14, [ range(5, 13, [ character(5, 8, [ single_quote(5, 6), inner_chr(6, 7), single_quote(7, 8) ]), range_operator(8, 10), character(10, 13, [ single_quote(10, 11), inner_chr(11, 12), single_quote(12, 13) ]) ]) ]), sequence_operator(14, 15), term(16, 24, [ negative_predicate_operator(16, 17), insensitive_string(17, 23, [ string(18, 23, [ quote(18, 19), inner_str(19, 22), quote(22, 23) ]) ]) ]), sequence_operator(24, 25), term(26, 35, [ opening_paren(26, 27), expression(27, 32, [ term(27, 29, [ identifier(27, 28) ]), choice_operator(29, 30), term(31, 32, [ identifier(31, 32) ]) ]), closing_paren(32, 33), repeat_operator(33, 34), optional_operator(34, 35) ]) ]) ] }; } #[test] fn repeat_exact() { parses_to! { parser: PestParser, input: "{1}", rule: Rule::repeat_exact, tokens: [ repeat_exact(0, 3, [ opening_brace(0, 1), number(1, 2), closing_brace(2, 3) ]) ] }; } #[test] fn repeat_min() { parses_to! { parser: PestParser, input: "{2,}", rule: Rule::repeat_min, tokens: [ repeat_min(0, 4, [ opening_brace(0,1), number(1,2), comma(2,3), closing_brace(3,4) ]) ] } } #[test] fn repeat_max() { parses_to! { parser: PestParser, input: "{, 3}", rule: Rule::repeat_max, tokens: [ repeat_max(0, 5, [ opening_brace(0,1), comma(1,2), number(3,4), closing_brace(4,5) ]) ] } } #[test] fn repeat_min_max() { parses_to! { parser: PestParser, input: "{1, 2}", rule: Rule::repeat_min_max, tokens: [ repeat_min_max(0, 6, [ opening_brace(0, 1), number(1, 2), comma(2, 3), number(4, 5), closing_brace(5, 6) ]) ] }; } #[test] fn push() { parses_to! { parser: PestParser, input: "PUSH ( a )", rule: Rule::_push, tokens: [ _push(0, 10, [ opening_paren(5, 6), expression(7, 9, [ term(7, 9, [ identifier(7, 8) ]) ]), closing_paren(9, 10) ]) ] }; } #[test] fn peek_slice_all() { parses_to! { parser: PestParser, input: "PEEK[..]", rule: Rule::peek_slice, tokens: [ peek_slice(0, 8, [ opening_brack(4, 5), range_operator(5, 7), closing_brack(7, 8) ]) ] }; } #[test] fn peek_slice_start() { parses_to! { parser: PestParser, input: "PEEK[1..]", rule: Rule::peek_slice, tokens: [ peek_slice(0, 9, [ opening_brack(4, 5), integer(5, 6), range_operator(6, 8), closing_brack(8, 9) ]) ] }; } #[test] fn peek_slice_end() { parses_to! { parser: PestParser, input: "PEEK[ ..-1]", rule: Rule::peek_slice, tokens: [ peek_slice(0, 11, [ opening_brack(4, 5), range_operator(6, 8), integer(8, 10), closing_brack(10, 11) ]) ] }; } #[test] fn peek_slice_start_end() { parses_to! { parser: PestParser, input: "PEEK[-5..10]", rule: Rule::peek_slice, tokens: [ peek_slice(0, 12, [ opening_brack(4, 5), integer(5, 7), range_operator(7, 9), integer(9, 11), closing_brack(11, 12) ]) ] }; } #[test] fn identifier() { parses_to! { parser: PestParser, input: "_a8943", rule: Rule::identifier, tokens: [ identifier(0, 6) ] }; } #[test] fn string() { parses_to! { parser: PestParser, input: "\"aaaaa\\n\\r\\t\\\\\\0\\'\\\"\\x0F\\u{123abC}\\u{12}aaaaa\"", rule: Rule::string, tokens: [ string(0, 46, [ quote(0, 1), inner_str(1, 45), quote(45, 46) ]) ] }; } #[test] fn insensitive_string() { parses_to! { parser: PestParser, input: "^ \"\\\"hi\"", rule: Rule::insensitive_string, tokens: [ insensitive_string(0, 9, [ string(3, 9, [ quote(3, 4), inner_str(4, 8), quote(8, 9) ]) ]) ] }; } #[test] fn range() { parses_to! { parser: PestParser, input: "'\\n' .. '\\x1a'", rule: Rule::range, tokens: [ range(0, 14, [ character(0, 4, [ single_quote(0, 1), inner_chr(1, 3), single_quote(3, 4) ]), range_operator(5, 7), character(8, 14, [ single_quote(8, 9), inner_chr(9, 13), single_quote(13, 14) ]) ]) ] }; } #[test] fn character() { parses_to! { parser: PestParser, input: "'\\u{123abC}'", rule: Rule::character, tokens: [ character(0, 12, [ single_quote(0, 1), inner_chr(1, 11), single_quote(11, 12) ]) ] }; } #[test] fn number() { parses_to! { parser: PestParser, input: "0123", rule: Rule::number, tokens: [ number(0, 4) ] }; } #[test] fn comment() { parses_to! { parser: PestParser, input: "a ~ // asda\n b", rule: Rule::expression, tokens: [ expression(0, 17, [ term(0, 2, [ identifier(0, 1) ]), sequence_operator(2, 3), term(16, 17, [ identifier(16, 17) ]) ]) ] }; } #[test] fn grammar_doc_and_line_doc() { let input = "//! hello\n/// world\na = { \"a\" }"; parses_to! { parser: PestParser, input: input, rule: Rule::grammar_rules, tokens: [ grammar_doc(0, 9, [ inner_doc(4, 9), ]), grammar_rule(10, 19, [ line_doc(10, 19, [ inner_doc(14, 19), ]), ]), grammar_rule(20, 31, [ identifier(20, 21), assignment_operator(22, 23), opening_brace(24, 25), expression(26, 30, [ term(26, 30, [ string(26, 29, [ quote(26, 27), inner_str(27, 28), quote(28, 29) ]) ]) ]), closing_brace(30, 31), ]) ] }; } #[test] fn wrong_identifier() { fails_with! { parser: PestParser, input: "0", rule: Rule::grammar_rules, positives: vec![Rule::EOI, Rule::grammar_rule, Rule::grammar_doc], negatives: vec![], pos: 0 }; } #[test] fn missing_assignment_operator() { fails_with! { parser: PestParser, input: "a {}", rule: Rule::grammar_rules, positives: vec![Rule::assignment_operator], negatives: vec![], pos: 2 }; } #[test] fn wrong_modifier() { fails_with! { parser: PestParser, input: "a = *{}", rule: Rule::grammar_rules, positives: vec![ Rule::opening_brace, Rule::silent_modifier, Rule::atomic_modifier, Rule::compound_atomic_modifier, Rule::non_atomic_modifier ], negatives: vec![], pos: 4 }; } #[test] fn missing_opening_brace() { fails_with! { parser: PestParser, input: "a = _", rule: Rule::grammar_rules, positives: vec![Rule::opening_brace], negatives: vec![], pos: 5 }; } #[test] fn empty_rule() { fails_with! { parser: PestParser, input: "a = {}", rule: Rule::grammar_rules, positives: vec![Rule::expression], negatives: vec![], pos: 5 }; } #[test] fn missing_rhs() { fails_with! { parser: PestParser, input: "a = { b ~ }", rule: Rule::grammar_rules, positives: vec![Rule::term], negatives: vec![], pos: 10 }; } #[test] fn incorrect_prefix() { fails_with! { parser: PestParser, input: "a = { ~ b}", rule: Rule::grammar_rules, positives: vec![Rule::expression], negatives: vec![], pos: 6 }; } #[test] fn wrong_op() { fails_with! { parser: PestParser, input: "a = { b % }", rule: Rule::grammar_rules, positives: vec![ Rule::opening_brace, Rule::closing_brace, Rule::sequence_operator, Rule::choice_operator, Rule::optional_operator, Rule::repeat_operator, Rule::repeat_once_operator ], negatives: vec![], pos: 8 }; } #[test] fn missing_closing_paren() { fails_with! { parser: PestParser, input: "a = { (b }", rule: Rule::grammar_rules, positives: vec![ Rule::opening_brace, Rule::closing_paren, Rule::sequence_operator, Rule::choice_operator, Rule::optional_operator, Rule::repeat_operator, Rule::repeat_once_operator ], negatives: vec![], pos: 9 }; } #[test] fn missing_term() { fails_with! { parser: PestParser, input: "a = { ! }", rule: Rule::grammar_rules, positives: vec![ Rule::opening_paren, Rule::positive_predicate_operator, Rule::negative_predicate_operator, Rule::_push, Rule::peek_slice, Rule::identifier, Rule::insensitive_string, Rule::quote, Rule::single_quote ], negatives: vec![], pos: 8 }; } #[test] fn string_missing_ending_quote() { fails_with! { parser: PestParser, input: "a = { \" }", rule: Rule::grammar_rules, positives: vec![Rule::quote], negatives: vec![], pos: 9 }; } #[test] fn insensitive_missing_string() { fails_with! { parser: PestParser, input: "a = { ^ }", rule: Rule::grammar_rules, positives: vec![Rule::quote], negatives: vec![], pos: 8 }; } #[test] fn char_missing_ending_single_quote() { fails_with! { parser: PestParser, input: "a = { \' }", rule: Rule::grammar_rules, positives: vec![Rule::single_quote], negatives: vec![], pos: 8 }; } #[test] fn range_missing_range_operator() { fails_with! { parser: PestParser, input: "a = { \'a\' }", rule: Rule::grammar_rules, positives: vec![Rule::range_operator], negatives: vec![], pos: 10 }; } #[test] fn wrong_postfix() { fails_with! { parser: PestParser, input: "a = { a& }", rule: Rule::grammar_rules, positives: vec![ Rule::opening_brace, Rule::closing_brace, Rule::sequence_operator, Rule::choice_operator, Rule::optional_operator, Rule::repeat_operator, Rule::repeat_once_operator ], negatives: vec![], pos: 7 }; } #[test] fn node_tag() { parses_to! { parser: PestParser, input: "#a = a", rule: Rule::expression, tokens: [ expression(0, 6, [ term(0, 6, [ tag_id(0, 2), assignment_operator(3, 4), identifier(5, 6) ]) ]) ] }; } #[test] fn incomplete_node_tag() { fails_with! { parser: PestParser, input: "a = { # }", rule: Rule::grammar_rules, positives: vec![ Rule::expression ], negatives: vec![], pos: 6 }; } #[test] fn incomplete_node_tag_assignment() { fails_with! { parser: PestParser, input: "a = { #a = }", rule: Rule::grammar_rules, positives: vec![ Rule::opening_paren, Rule::positive_predicate_operator, Rule::negative_predicate_operator, Rule::_push, Rule::peek_slice, Rule::identifier, Rule::insensitive_string, Rule::quote, Rule::single_quote ], negatives: vec![], pos: 11 }; } #[test] fn incomplete_node_tag_pound_key() { fails_with! { parser: PestParser, input: "a = { a = a }", rule: Rule::grammar_rules, positives: vec![ Rule::opening_brace, Rule::closing_brace, Rule::sequence_operator, Rule::choice_operator, Rule::optional_operator, Rule::repeat_operator, Rule::repeat_once_operator ], negatives: vec![], pos: 8 }; } #[test] fn ast() { let input = r#" /// This is line comment /// This is rule rule = _{ a{1} ~ "a"{3,} ~ b{, 2} ~ "b"{1, 2} | !(^"c" | PUSH('d'..'e'))?* } "#; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); let ast = consume_rules_with_spans(pairs).unwrap(); let ast: Vec<_> = ast.into_iter().map(convert_rule).collect(); assert_eq!( ast, vec![AstRule { name: "rule".to_owned(), ty: RuleType::Silent, expr: Expr::Choice( Box::new(Expr::Seq( Box::new(Expr::Seq( Box::new(Expr::Seq( Box::new(Expr::RepExact(Box::new(Expr::Ident("a".to_owned())), 1)), Box::new(Expr::RepMin(Box::new(Expr::Str("a".to_owned())), 3)) )), Box::new(Expr::RepMax(Box::new(Expr::Ident("b".to_owned())), 2)) )), Box::new(Expr::RepMinMax(Box::new(Expr::Str("b".to_owned())), 1, 2)) )), Box::new(Expr::NegPred(Box::new(Expr::Rep(Box::new(Expr::Opt( Box::new(Expr::Choice( Box::new(Expr::Insens("c".to_owned())), Box::new(Expr::Push(Box::new(Expr::Range( "d".to_owned(), "e".to_owned() )))) )) )))))) ) },] ); } #[test] fn ast_peek_slice() { let input = "rule = _{ PEEK[-04..] ~ PEEK[..3] }"; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); let ast = consume_rules_with_spans(pairs).unwrap(); let ast: Vec<_> = ast.into_iter().map(convert_rule).collect(); assert_eq!( ast, vec![AstRule { name: "rule".to_owned(), ty: RuleType::Silent, expr: Expr::Seq( Box::new(Expr::PeekSlice(-4, None)), Box::new(Expr::PeekSlice(0, Some(3))), ), }], ); } #[test] #[should_panic(expected = "grammar error --> 1:13 | 1 | rule = { \"\"{4294967297} } | ^--------^ | = number cannot overflow u32")] fn repeat_exact_overflow() { let input = "rule = { \"\"{4294967297} }"; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); unwrap_or_report(consume_rules_with_spans(pairs)); } #[test] #[should_panic(expected = "grammar error --> 1:13 | 1 | rule = { \"\"{0} } | ^ | = cannot repeat 0 times")] fn repeat_exact_zero() { let input = "rule = { \"\"{0} }"; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); unwrap_or_report(consume_rules_with_spans(pairs)); } #[test] #[should_panic(expected = "grammar error --> 1:13 | 1 | rule = { \"\"{4294967297,} } | ^--------^ | = number cannot overflow u32")] fn repeat_min_overflow() { let input = "rule = { \"\"{4294967297,} }"; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); unwrap_or_report(consume_rules_with_spans(pairs)); } #[test] #[should_panic(expected = "grammar error --> 1:14 | 1 | rule = { \"\"{,4294967297} } | ^--------^ | = number cannot overflow u32")] fn repeat_max_overflow() { let input = "rule = { \"\"{,4294967297} }"; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); unwrap_or_report(consume_rules_with_spans(pairs)); } #[test] #[should_panic(expected = "grammar error --> 1:14 | 1 | rule = { \"\"{,0} } | ^ | = cannot repeat 0 times")] fn repeat_max_zero() { let input = "rule = { \"\"{,0} }"; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); unwrap_or_report(consume_rules_with_spans(pairs)); } #[test] #[should_panic(expected = "grammar error --> 1:13 | 1 | rule = { \"\"{4294967297,4294967298} } | ^--------^ | = number cannot overflow u32")] fn repeat_min_max_overflow() { let input = "rule = { \"\"{4294967297,4294967298} }"; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); unwrap_or_report(consume_rules_with_spans(pairs)); } #[test] #[should_panic(expected = "grammar error --> 1:15 | 1 | rule = { \"\"{0,0} } | ^ | = cannot repeat 0 times")] fn repeat_min_max_zero() { let input = "rule = { \"\"{0,0} }"; let pairs = PestParser::parse(Rule::grammar_rules, input).unwrap(); unwrap_or_report(consume_rules_with_spans(pairs)); } #[test] fn unescape_all() { let string = r"a\nb\x55c\u{111}d"; assert_eq!(unescape(string), Some("a\nb\x55c\u{111}d".to_owned())); } #[test] fn unescape_empty_escape() { let string = r"\"; assert_eq!(unescape(string), None); } #[test] fn unescape_wrong_escape() { let string = r"\w"; assert_eq!(unescape(string), None); } #[test] fn unescape_backslash() { let string = "\\\\"; assert_eq!(unescape(string), Some("\\".to_owned())); } #[test] fn unescape_return() { let string = "\\r"; assert_eq!(unescape(string), Some("\r".to_owned())); } #[test] fn unescape_tab() { let string = "\\t"; assert_eq!(unescape(string), Some("\t".to_owned())); } #[test] fn unescape_null() { let string = "\\0"; assert_eq!(unescape(string), Some("\0".to_owned())); } #[test] fn unescape_single_quote() { let string = "\\'"; assert_eq!(unescape(string), Some("\'".to_owned())); } #[test] fn unescape_wrong_byte() { let string = r"\xfg"; assert_eq!(unescape(string), None); } #[test] fn unescape_short_byte() { let string = r"\xf"; assert_eq!(unescape(string), None); } #[test] fn unescape_no_open_brace_unicode() { let string = r"\u11"; assert_eq!(unescape(string), None); } #[test] fn unescape_no_close_brace_unicode() { let string = r"\u{11"; assert_eq!(unescape(string), None); } #[test] fn unescape_short_unicode() { let string = r"\u{1}"; assert_eq!(unescape(string), None); } #[test] fn unescape_long_unicode() { let string = r"\u{1111111}"; assert_eq!(unescape(string), None); } #[test] fn handles_deep_nesting() { let sample1 = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/resources/test/fuzzsample1.grammar" )); let sample2 = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/resources/test/fuzzsample2.grammar" )); let sample3 = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/resources/test/fuzzsample3.grammar" )); let sample4 = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/resources/test/fuzzsample4.grammar" )); let sample5 = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/resources/test/fuzzsample5.grammar" )); const ERROR: &str = "call limit reached"; pest::set_call_limit(Some(5_000usize.try_into().unwrap())); let s1 = parse(Rule::grammar_rules, sample1); assert!(s1.is_err()); assert_eq!(s1.unwrap_err().variant.message(), ERROR); let s2 = parse(Rule::grammar_rules, sample2); assert!(s2.is_err()); assert_eq!(s2.unwrap_err().variant.message(), ERROR); let s3 = parse(Rule::grammar_rules, sample3); assert!(s3.is_err()); assert_eq!(s3.unwrap_err().variant.message(), ERROR); let s4 = parse(Rule::grammar_rules, sample4); assert!(s4.is_err()); assert_eq!(s4.unwrap_err().variant.message(), ERROR); let s5 = parse(Rule::grammar_rules, sample5); assert!(s5.is_err()); assert_eq!(s5.unwrap_err().variant.message(), ERROR); } } pest_meta-2.7.4/src/validator.rs000064400000000000000000001602331046102023000147460ustar 00000000000000// pest. The Elegant Parser // Copyright (c) 2018 Dragoș Tiselice // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. //! Helpers for validating pest grammars that could help with debugging //! and provide a more user-friendly error message. use once_cell::sync::Lazy; use std::collections::{HashMap, HashSet}; use pest::error::{Error, ErrorVariant, InputLocation}; use pest::iterators::Pairs; use pest::unicode::unicode_property_names; use pest::Span; use crate::parser::{ParserExpr, ParserNode, ParserRule, Rule}; static RUST_KEYWORDS: Lazy> = Lazy::new(|| { [ "abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do", "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pure", "pub", "ref", "return", "Self", "self", "sizeof", "static", "struct", "super", "trait", "true", "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield", ] .iter() .cloned() .collect() }); static PEST_KEYWORDS: Lazy> = Lazy::new(|| { [ "_", "ANY", "DROP", "EOI", "PEEK", "PEEK_ALL", "POP", "POP_ALL", "PUSH", "SOI", ] .iter() .cloned() .collect() }); static BUILTINS: Lazy> = Lazy::new(|| { [ "ANY", "DROP", "EOI", "PEEK", "PEEK_ALL", "POP", "POP_ALL", "SOI", "ASCII_DIGIT", "ASCII_NONZERO_DIGIT", "ASCII_BIN_DIGIT", "ASCII_OCT_DIGIT", "ASCII_HEX_DIGIT", "ASCII_ALPHA_LOWER", "ASCII_ALPHA_UPPER", "ASCII_ALPHA", "ASCII_ALPHANUMERIC", "ASCII", "NEWLINE", ] .iter() .cloned() .chain(unicode_property_names()) .collect::>() }); /// It checks the parsed grammar for common mistakes: /// - using Pest keywords /// - duplicate rules /// - undefined rules /// /// It returns a `Result` with a `Vec` of `Error`s if any of the above is found. /// If no errors are found, it returns the vector of names of used builtin rules. pub fn validate_pairs(pairs: Pairs<'_, Rule>) -> Result, Vec>> { let definitions: Vec<_> = pairs .clone() .filter(|pair| pair.as_rule() == Rule::grammar_rule) .map(|pair| pair.into_inner().next().unwrap()) .filter(|pair| pair.as_rule() != Rule::line_doc) .map(|pair| pair.as_span()) .collect(); let called_rules: Vec<_> = pairs .clone() .filter(|pair| pair.as_rule() == Rule::grammar_rule) .flat_map(|pair| { pair.into_inner() .flatten() .skip(1) .filter(|pair| pair.as_rule() == Rule::identifier) .map(|pair| pair.as_span()) }) .collect(); let mut errors = vec![]; errors.extend(validate_pest_keywords(&definitions)); errors.extend(validate_already_defined(&definitions)); errors.extend(validate_undefined(&definitions, &called_rules)); if !errors.is_empty() { return Err(errors); } let definitions: HashSet<_> = definitions.iter().map(|span| span.as_str()).collect(); let called_rules: HashSet<_> = called_rules.iter().map(|span| span.as_str()).collect(); let defaults = called_rules.difference(&definitions); Ok(defaults.cloned().collect()) } /// Validates that the given `definitions` do not contain any Rust keywords. #[allow(clippy::ptr_arg)] #[deprecated = "Rust keywords are no longer restricted from the pest grammar"] pub fn validate_rust_keywords(definitions: &Vec>) -> Vec> { let mut errors = vec![]; for definition in definitions { let name = definition.as_str(); if RUST_KEYWORDS.contains(name) { errors.push(Error::new_from_span( ErrorVariant::CustomError { message: format!("{} is a rust keyword", name), }, *definition, )) } } errors } /// Validates that the given `definitions` do not contain any Pest keywords. #[allow(clippy::ptr_arg)] pub fn validate_pest_keywords(definitions: &Vec>) -> Vec> { let mut errors = vec![]; for definition in definitions { let name = definition.as_str(); if PEST_KEYWORDS.contains(name) { errors.push(Error::new_from_span( ErrorVariant::CustomError { message: format!("{} is a pest keyword", name), }, *definition, )) } } errors } /// Validates that the given `definitions` do not contain any duplicate rules. #[allow(clippy::ptr_arg)] pub fn validate_already_defined(definitions: &Vec>) -> Vec> { let mut errors = vec![]; let mut defined = HashSet::new(); for definition in definitions { let name = definition.as_str(); if defined.contains(&name) { errors.push(Error::new_from_span( ErrorVariant::CustomError { message: format!("rule {} already defined", name), }, *definition, )) } else { defined.insert(name); } } errors } /// Validates that the given `definitions` do not contain any undefined rules. #[allow(clippy::ptr_arg)] pub fn validate_undefined<'i>( definitions: &Vec>, called_rules: &Vec>, ) -> Vec> { let mut errors = vec![]; let definitions: HashSet<_> = definitions.iter().map(|span| span.as_str()).collect(); for rule in called_rules { let name = rule.as_str(); if !definitions.contains(name) && !BUILTINS.contains(name) { errors.push(Error::new_from_span( ErrorVariant::CustomError { message: format!("rule {} is undefined", name), }, *rule, )) } } errors } /// Validates the abstract syntax tree for common mistakes: /// - infinite repetitions /// - choices that cannot be reached /// - left recursion #[allow(clippy::ptr_arg)] pub fn validate_ast<'a, 'i: 'a>(rules: &'a Vec>) -> Vec> { let mut errors = vec![]; // WARNING: validate_{repetition,choice,whitespace_comment} // use is_non_failing and is_non_progressing breaking assumptions: // - for every `ParserExpr::RepMinMax(inner,min,max)`, // `min<=max` was not checked // - left recursion was not checked // - Every expression might not be checked errors.extend(validate_repetition(rules)); errors.extend(validate_choices(rules)); errors.extend(validate_whitespace_comment(rules)); errors.extend(validate_left_recursion(rules)); errors.sort_by_key(|error| match error.location { InputLocation::Span(span) => span, _ => unreachable!(), }); errors } /// Checks if `expr` is non-progressing, that is the expression does not /// consume any input or any stack. This includes expressions matching the empty input, /// `SOI` and ̀ `EOI`, predicates and repetitions. /// /// # Example /// /// ```pest /// not_progressing_1 = { "" } /// not_progressing_2 = { "a"? } /// not_progressing_3 = { !"a" } /// ``` /// /// # Assumptions /// - In `ParserExpr::RepMinMax(inner,min,max)`, `min<=max` /// - All rules identiers have a matching definition /// - There is no left-recursion (if only this one is broken returns false) /// - Every expression is being checked fn is_non_progressing<'i>( expr: &ParserExpr<'i>, rules: &HashMap>, trace: &mut Vec, ) -> bool { match *expr { ParserExpr::Str(ref string) | ParserExpr::Insens(ref string) => string.is_empty(), ParserExpr::Ident(ref ident) => { if ident == "SOI" || ident == "EOI" { return true; } if !trace.contains(ident) { if let Some(node) = rules.get(ident) { trace.push(ident.clone()); let result = is_non_progressing(&node.expr, rules, trace); trace.pop().unwrap(); return result; } // else // the ident is // - "POP","PEEK" => false // the slice being checked is not non_progressing since every // PUSH is being checked (assumption 4) and the expr // of a PUSH has to be non_progressing. // - "POPALL", "PEEKALL" => false // same as "POP", "PEEK" unless the following: // BUG: if the stack is empty they are non_progressing // - "DROP" => false doesn't consume the input but consumes the stack, // - "ANY", "ASCII_*", UNICODE categories, "NEWLINE" => false // - referring to another rule that is undefined (breaks assumption) } // else referring to another rule that was already seen. // this happens only if there is a left-recursion // that is only if an assumption is broken, // WARNING: we can choose to return false, but that might // cause bugs into the left_recursion check false } ParserExpr::Seq(ref lhs, ref rhs) => { is_non_progressing(&lhs.expr, rules, trace) && is_non_progressing(&rhs.expr, rules, trace) } ParserExpr::Choice(ref lhs, ref rhs) => { is_non_progressing(&lhs.expr, rules, trace) || is_non_progressing(&rhs.expr, rules, trace) } // WARNING: the predicate indeed won't make progress on input but it // might progress on the stack // ex: @{ PUSH(ANY) ~ (&(DROP))* ~ ANY }, input="AA" // Notice that this is ex not working as of now, the debugger seems // to run into an infinite loop on it ParserExpr::PosPred(_) | ParserExpr::NegPred(_) => true, ParserExpr::Rep(_) | ParserExpr::Opt(_) | ParserExpr::RepMax(_, _) => true, // it either always fail (failing is progressing) // or always match at least a character ParserExpr::Range(_, _) => false, ParserExpr::PeekSlice(_, _) => { // the slice being checked is not non_progressing since every // PUSH is being checked (assumption 4) and the expr // of a PUSH has to be non_progressing. // BUG: if the slice is of size 0, or the stack is not large // enough it might be non-progressing false } ParserExpr::RepExact(ref inner, min) | ParserExpr::RepMin(ref inner, min) | ParserExpr::RepMinMax(ref inner, min, _) => { min == 0 || is_non_progressing(&inner.expr, rules, trace) } ParserExpr::Push(ref inner) => is_non_progressing(&inner.expr, rules, trace), ParserExpr::RepOnce(ref inner) => is_non_progressing(&inner.expr, rules, trace), #[cfg(feature = "grammar-extras")] ParserExpr::NodeTag(ref inner, _) => is_non_progressing(&inner.expr, rules, trace), } } /// Checks if `expr` is non-failing, that is it matches any input. /// /// # Example /// /// ```pest /// non_failing_1 = { "" } /// ``` /// /// # Assumptions /// - In `ParserExpr::RepMinMax(inner,min,max)`, `min<=max` /// - In `ParserExpr::PeekSlice(max,Some(min))`, `max>=min` /// - All rules identiers have a matching definition /// - There is no left-recursion /// - All rules are being checked fn is_non_failing<'i>( expr: &ParserExpr<'i>, rules: &HashMap>, trace: &mut Vec, ) -> bool { match *expr { ParserExpr::Str(ref string) | ParserExpr::Insens(ref string) => string.is_empty(), ParserExpr::Ident(ref ident) => { if !trace.contains(ident) { if let Some(node) = rules.get(ident) { trace.push(ident.clone()); let result = is_non_failing(&node.expr, rules, trace); trace.pop().unwrap(); result } else { // else // the ident is // - "POP","PEEK" => false // the slice being checked is not non_failing since every // PUSH is being checked (assumption 4) and the expr // of a PUSH has to be non_failing. // - "POP_ALL", "PEEK_ALL" => false // same as "POP", "PEEK" unless the following: // BUG: if the stack is empty they are non_failing // - "DROP" => false // - "ANY", "ASCII_*", UNICODE categories, "NEWLINE", // "SOI", "EOI" => false // - referring to another rule that is undefined (breaks assumption) // WARNING: might want to introduce a panic or report the error false } } else { // referring to another rule R that was already seen // WARNING: this might mean there is a circular non-failing path // it's not obvious wether this can happen without left-recursion // and thus breaking the assumption. Until there is answer to // this, to avoid changing behaviour we return: false } } ParserExpr::Opt(_) => true, ParserExpr::Rep(_) => true, ParserExpr::RepMax(_, _) => true, ParserExpr::Seq(ref lhs, ref rhs) => { is_non_failing(&lhs.expr, rules, trace) && is_non_failing(&rhs.expr, rules, trace) } ParserExpr::Choice(ref lhs, ref rhs) => { is_non_failing(&lhs.expr, rules, trace) || is_non_failing(&rhs.expr, rules, trace) } // it either always fail // or always match at least a character ParserExpr::Range(_, _) => false, ParserExpr::PeekSlice(_, _) => { // the slice being checked is not non_failing since every // PUSH is being checked (assumption 4) and the expr // of a PUSH has to be non_failing. // BUG: if the slice is of size 0, or the stack is not large // enough it might be non-failing false } ParserExpr::RepExact(ref inner, min) | ParserExpr::RepMin(ref inner, min) | ParserExpr::RepMinMax(ref inner, min, _) => { min == 0 || is_non_failing(&inner.expr, rules, trace) } // BUG: the predicate may always fail, resulting in this expr non_failing // ex of always failing predicates : // @{EOI ~ ANY | ANY ~ SOI | &("A") ~ &("B") | 'z'..'a'} ParserExpr::NegPred(_) => false, ParserExpr::RepOnce(ref inner) => is_non_failing(&inner.expr, rules, trace), ParserExpr::Push(ref inner) | ParserExpr::PosPred(ref inner) => { is_non_failing(&inner.expr, rules, trace) } #[cfg(feature = "grammar-extras")] ParserExpr::NodeTag(ref inner, _) => is_non_failing(&inner.expr, rules, trace), } } fn validate_repetition<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec> { let mut result = vec![]; let map = to_hash_map(rules); for rule in rules { let mut errors = rule.node .clone() .filter_map_top_down(|node| match node.expr { ParserExpr::Rep(ref other) | ParserExpr::RepOnce(ref other) | ParserExpr::RepMin(ref other, _) => { if is_non_failing(&other.expr, &map, &mut vec![]) { Some(Error::new_from_span( ErrorVariant::CustomError { message: "expression inside repetition cannot fail and will repeat \ infinitely" .to_owned() }, node.span )) } else if is_non_progressing(&other.expr, &map, &mut vec![]) { Some(Error::new_from_span( ErrorVariant::CustomError { message: "expression inside repetition is non-progressing and will repeat \ infinitely" .to_owned(), }, node.span )) } else { None } } _ => None }); result.append(&mut errors); } result } fn validate_choices<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec> { let mut result = vec![]; let map = to_hash_map(rules); for rule in rules { let mut errors = rule .node .clone() .filter_map_top_down(|node| match node.expr { ParserExpr::Choice(ref lhs, _) => { let node = match lhs.expr { ParserExpr::Choice(_, ref rhs) => rhs, _ => lhs, }; if is_non_failing(&node.expr, &map, &mut vec![]) { Some(Error::new_from_span( ErrorVariant::CustomError { message: "expression cannot fail; following choices cannot be reached" .to_owned(), }, node.span, )) } else { None } } _ => None, }); result.append(&mut errors); } result } fn validate_whitespace_comment<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec> { let map = to_hash_map(rules); rules .iter() .filter_map(|rule| { if rule.name == "WHITESPACE" || rule.name == "COMMENT" { if is_non_failing(&rule.node.expr, &map, &mut vec![]) { Some(Error::new_from_span( ErrorVariant::CustomError { message: format!( "{} cannot fail and will repeat infinitely", &rule.name ), }, rule.node.span, )) } else if is_non_progressing(&rule.node.expr, &map, &mut vec![]) { Some(Error::new_from_span( ErrorVariant::CustomError { message: format!( "{} is non-progressing and will repeat infinitely", &rule.name ), }, rule.node.span, )) } else { None } } else { None } }) .collect() } fn validate_left_recursion<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> Vec> { left_recursion(to_hash_map(rules)) } fn to_hash_map<'a, 'i: 'a>(rules: &'a [ParserRule<'i>]) -> HashMap> { rules.iter().map(|r| (r.name.clone(), &r.node)).collect() } fn left_recursion<'a, 'i: 'a>(rules: HashMap>) -> Vec> { fn check_expr<'a, 'i: 'a>( node: &'a ParserNode<'i>, rules: &'a HashMap>, trace: &mut Vec, ) -> Option> { match node.expr.clone() { ParserExpr::Ident(other) => { if trace[0] == other { trace.push(other); let chain = trace .iter() .map(|ident| ident.as_ref()) .collect::>() .join(" -> "); return Some(Error::new_from_span( ErrorVariant::CustomError { message: format!( "rule {} is left-recursive ({}); pest::pratt_parser might be useful \ in this case", node.span.as_str(), chain ) }, node.span )); } if !trace.contains(&other) { if let Some(node) = rules.get(&other) { trace.push(other); let result = check_expr(node, rules, trace); trace.pop().unwrap(); return result; } } None } ParserExpr::Seq(ref lhs, ref rhs) => { if is_non_failing(&lhs.expr, rules, &mut vec![trace.last().unwrap().clone()]) { check_expr(rhs, rules, trace) } else { check_expr(lhs, rules, trace) } } ParserExpr::Choice(ref lhs, ref rhs) => { check_expr(lhs, rules, trace).or_else(|| check_expr(rhs, rules, trace)) } ParserExpr::Rep(ref node) => check_expr(node, rules, trace), ParserExpr::RepOnce(ref node) => check_expr(node, rules, trace), ParserExpr::Opt(ref node) => check_expr(node, rules, trace), ParserExpr::PosPred(ref node) => check_expr(node, rules, trace), ParserExpr::NegPred(ref node) => check_expr(node, rules, trace), ParserExpr::Push(ref node) => check_expr(node, rules, trace), _ => None, } } let mut errors = vec![]; for (name, node) in &rules { let name = name.clone(); if let Some(error) = check_expr(node, &rules, &mut vec![name]) { errors.push(error); } } errors } #[cfg(test)] mod tests { use super::super::parser::{consume_rules, PestParser}; use super::super::unwrap_or_report; use super::*; use pest::Parser; #[test] #[should_panic(expected = "grammar error --> 1:1 | 1 | ANY = { \"a\" } | ^-^ | = ANY is a pest keyword")] fn pest_keyword() { let input = "ANY = { \"a\" }"; unwrap_or_report(validate_pairs( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:13 | 1 | a = { \"a\" } a = { \"a\" } | ^ | = rule a already defined")] fn already_defined() { let input = "a = { \"a\" } a = { \"a\" }"; unwrap_or_report(validate_pairs( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:7 | 1 | a = { b } | ^ | = rule b is undefined")] fn undefined() { let input = "a = { b }"; unwrap_or_report(validate_pairs( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] fn valid_recursion() { let input = "a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"b\") ~ a }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:16 | 1 | WHITESPACE = { \"\" } | ^^ | = WHITESPACE cannot fail and will repeat infinitely")] fn non_failing_whitespace() { let input = "WHITESPACE = { \"\" }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:13 | 1 | COMMENT = { SOI } | ^-^ | = COMMENT is non-progressing and will repeat infinitely")] fn non_progressing_comment() { let input = "COMMENT = { SOI }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] fn non_progressing_empty_string() { assert!(is_non_failing( &ParserExpr::Insens("".into()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::Str("".into()), &HashMap::new(), &mut Vec::new() )); } #[test] fn progressing_non_empty_string() { assert!(!is_non_progressing( &ParserExpr::Insens("non empty".into()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing( &ParserExpr::Str("non empty".into()), &HashMap::new(), &mut Vec::new() )); } #[test] fn non_progressing_soi_eoi() { assert!(is_non_progressing( &ParserExpr::Ident("SOI".into()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::Ident("EOI".into()), &HashMap::new(), &mut Vec::new() )); } #[test] fn non_progressing_predicates() { let progressing = ParserExpr::Str("A".into()); assert!(is_non_progressing( &ParserExpr::PosPred(Box::new(ParserNode { expr: progressing.clone(), span: Span::new(" ", 0, 1).unwrap(), })), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::NegPred(Box::new(ParserNode { expr: progressing, span: Span::new(" ", 0, 1).unwrap(), })), &HashMap::new(), &mut Vec::new() )); } #[test] fn non_progressing_0_length_repetitions() { let input_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("A".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_progressing( &input_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::Rep(input_progressing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::Opt(input_progressing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::RepExact(input_progressing_node.clone(), 0), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::RepMin(input_progressing_node.clone(), 0), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::RepMax(input_progressing_node.clone(), 0), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::RepMax(input_progressing_node.clone(), 17), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::RepMinMax(input_progressing_node.clone(), 0, 12), &HashMap::new(), &mut Vec::new() )); } #[test] fn non_progressing_nonzero_repetitions_with_non_progressing_expr() { let a = ""; let non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str(a.into()), span: Span::new(a, 0, 0).unwrap(), }); let exact = ParserExpr::RepExact(non_progressing_node.clone(), 7); let min = ParserExpr::RepMin(non_progressing_node.clone(), 23); let minmax = ParserExpr::RepMinMax(non_progressing_node.clone(), 12, 13); let reponce = ParserExpr::RepOnce(non_progressing_node); assert!(is_non_progressing(&exact, &HashMap::new(), &mut Vec::new())); assert!(is_non_progressing(&min, &HashMap::new(), &mut Vec::new())); assert!(is_non_progressing( &minmax, &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &reponce, &HashMap::new(), &mut Vec::new() )); } #[test] fn progressing_repetitions() { let a = "A"; let input_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str(a.into()), span: Span::new(a, 0, 1).unwrap(), }); let exact = ParserExpr::RepExact(input_progressing_node.clone(), 1); let min = ParserExpr::RepMin(input_progressing_node.clone(), 2); let minmax = ParserExpr::RepMinMax(input_progressing_node.clone(), 4, 5); let reponce = ParserExpr::RepOnce(input_progressing_node); assert!(!is_non_progressing( &exact, &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing(&min, &HashMap::new(), &mut Vec::new())); assert!(!is_non_progressing( &minmax, &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing( &reponce, &HashMap::new(), &mut Vec::new() )); } #[test] fn non_progressing_push() { let a = ""; let non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str(a.into()), span: Span::new(a, 0, 0).unwrap(), }); let push = ParserExpr::Push(non_progressing_node.clone()); assert!(is_non_progressing(&push, &HashMap::new(), &mut Vec::new())); } #[test] fn progressing_push() { let a = "i'm make progress"; let progressing_node = Box::new(ParserNode { expr: ParserExpr::Str(a.into()), span: Span::new(a, 0, 1).unwrap(), }); let push = ParserExpr::Push(progressing_node.clone()); assert!(!is_non_progressing(&push, &HashMap::new(), &mut Vec::new())); } #[test] fn node_tag_forwards_is_non_progressing() { let progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("i'm make progress".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_progressing( &progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_progressing( &non_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); #[cfg(feature = "grammar-extras")] { let progressing = ParserExpr::NodeTag(progressing_node.clone(), "TAG".into()); let non_progressing = ParserExpr::NodeTag(non_progressing_node.clone(), "TAG".into()); assert!(!is_non_progressing( &progressing, &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &non_progressing, &HashMap::new(), &mut Vec::new() )); } } #[test] fn progressing_range() { let progressing = ParserExpr::Range("A".into(), "Z".into()); let failing_is_progressing = ParserExpr::Range("Z".into(), "A".into()); assert!(!is_non_progressing( &progressing, &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing( &failing_is_progressing, &HashMap::new(), &mut Vec::new() )); } #[test] fn progressing_choice() { let left_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("i'm make progress".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_progressing( &left_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_progressing_node = Box::new(ParserNode { expr: ParserExpr::Ident("DROP".into()), span: Span::new("DROP", 0, 3).unwrap(), }); assert!(!is_non_progressing( &ParserExpr::Choice(left_progressing_node, right_progressing_node), &HashMap::new(), &mut Vec::new() )) } #[test] fn non_progressing_choices() { let left_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("i'm make progress".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_progressing( &left_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let left_non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_progressing( &left_non_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_progressing_node = Box::new(ParserNode { expr: ParserExpr::Ident("DROP".into()), span: Span::new("DROP", 0, 3).unwrap(), }); assert!(!is_non_progressing( &right_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Opt(Box::new(ParserNode { expr: ParserExpr::Str(" ".into()), span: Span::new(" ", 0, 1).unwrap(), })), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_progressing( &right_non_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::Choice(left_non_progressing_node.clone(), right_progressing_node), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::Choice(left_progressing_node, right_non_progressing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_progressing( &ParserExpr::Choice(left_non_progressing_node, right_non_progressing_node), &HashMap::new(), &mut Vec::new() )) } #[test] fn non_progressing_seq() { let left_non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("".into()), span: Span::new(" ", 0, 1).unwrap(), }); let right_non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Opt(Box::new(ParserNode { expr: ParserExpr::Str(" ".into()), span: Span::new(" ", 0, 1).unwrap(), })), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_progressing( &ParserExpr::Seq(left_non_progressing_node, right_non_progressing_node), &HashMap::new(), &mut Vec::new() )) } #[test] fn progressing_seqs() { let left_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("i'm make progress".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_progressing( &left_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let left_non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Str("".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_progressing( &left_non_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_progressing_node = Box::new(ParserNode { expr: ParserExpr::Ident("DROP".into()), span: Span::new("DROP", 0, 3).unwrap(), }); assert!(!is_non_progressing( &right_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_non_progressing_node = Box::new(ParserNode { expr: ParserExpr::Opt(Box::new(ParserNode { expr: ParserExpr::Str(" ".into()), span: Span::new(" ", 0, 1).unwrap(), })), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_progressing( &right_non_progressing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing( &ParserExpr::Seq(left_non_progressing_node, right_progressing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing( &ParserExpr::Seq(left_progressing_node.clone(), right_non_progressing_node), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing( &ParserExpr::Seq(left_progressing_node, right_progressing_node), &HashMap::new(), &mut Vec::new() )) } #[test] fn progressing_stack_operations() { assert!(!is_non_progressing( &ParserExpr::Ident("DROP".into()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing( &ParserExpr::Ident("PEEK".into()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_progressing( &ParserExpr::Ident("POP".into()), &HashMap::new(), &mut Vec::new() )) } #[test] fn non_failing_string() { let insens = ParserExpr::Insens("".into()); let string = ParserExpr::Str("".into()); assert!(is_non_failing(&insens, &HashMap::new(), &mut Vec::new())); assert!(is_non_failing(&string, &HashMap::new(), &mut Vec::new())) } #[test] fn failing_string() { assert!(!is_non_failing( &ParserExpr::Insens("i may fail!".into()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::Str("failure is not fatal".into()), &HashMap::new(), &mut Vec::new() )) } #[test] fn failing_stack_operations() { assert!(!is_non_failing( &ParserExpr::Ident("DROP".into()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::Ident("POP".into()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::Ident("PEEK".into()), &HashMap::new(), &mut Vec::new() )) } #[test] fn non_failing_zero_length_repetitions() { let failing = Box::new(ParserNode { expr: ParserExpr::Range("A".into(), "B".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_failing( &failing.clone().expr, &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::Opt(failing.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::Rep(failing.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepExact(failing.clone(), 0), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepMin(failing.clone(), 0), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepMax(failing.clone(), 0), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepMax(failing.clone(), 22), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepMinMax(failing.clone(), 0, 73), &HashMap::new(), &mut Vec::new() )); } #[test] fn non_failing_non_zero_repetitions_with_non_failing_expr() { let non_failing = Box::new(ParserNode { expr: ParserExpr::Opt(Box::new(ParserNode { expr: ParserExpr::Range("A".into(), "B".into()), span: Span::new(" ", 0, 1).unwrap(), })), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_failing( &non_failing.clone().expr, &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepOnce(non_failing.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepExact(non_failing.clone(), 1), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepMin(non_failing.clone(), 6), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::RepMinMax(non_failing.clone(), 32, 73), &HashMap::new(), &mut Vec::new() )); } #[test] #[cfg(feature = "grammar-extras")] fn failing_non_zero_repetitions() { let failing = Box::new(ParserNode { expr: ParserExpr::NodeTag( Box::new(ParserNode { expr: ParserExpr::Range("A".into(), "B".into()), span: Span::new(" ", 0, 1).unwrap(), }), "Tag".into(), ), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_failing( &failing.clone().expr, &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::RepOnce(failing.clone()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::RepExact(failing.clone(), 3), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::RepMin(failing.clone(), 14), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::RepMinMax(failing.clone(), 47, 73), &HashMap::new(), &mut Vec::new() )); } #[test] fn failing_choice() { let left_failing_node = Box::new(ParserNode { expr: ParserExpr::Str("i'm a failure".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_failing( &left_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_failing_node = Box::new(ParserNode { expr: ParserExpr::Ident("DROP".into()), span: Span::new("DROP", 0, 3).unwrap(), }); assert!(!is_non_failing( &ParserExpr::Choice(left_failing_node, right_failing_node), &HashMap::new(), &mut Vec::new() )) } #[test] fn non_failing_choices() { let left_failing_node = Box::new(ParserNode { expr: ParserExpr::Str("i'm a failure".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_failing( &left_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let left_non_failing_node = Box::new(ParserNode { expr: ParserExpr::Str("".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_failing( &left_non_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_failing_node = Box::new(ParserNode { expr: ParserExpr::Ident("DROP".into()), span: Span::new("DROP", 0, 3).unwrap(), }); assert!(!is_non_failing( &right_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_non_failing_node = Box::new(ParserNode { expr: ParserExpr::Opt(Box::new(ParserNode { expr: ParserExpr::Str(" ".into()), span: Span::new(" ", 0, 1).unwrap(), })), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_failing( &right_non_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::Choice(left_non_failing_node.clone(), right_failing_node), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::Choice(left_failing_node, right_non_failing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::Choice(left_non_failing_node, right_non_failing_node), &HashMap::new(), &mut Vec::new() )) } #[test] fn non_failing_seq() { let left_non_failing_node = Box::new(ParserNode { expr: ParserExpr::Str("".into()), span: Span::new(" ", 0, 1).unwrap(), }); let right_non_failing_node = Box::new(ParserNode { expr: ParserExpr::Opt(Box::new(ParserNode { expr: ParserExpr::Str(" ".into()), span: Span::new(" ", 0, 1).unwrap(), })), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_failing( &ParserExpr::Seq(left_non_failing_node, right_non_failing_node), &HashMap::new(), &mut Vec::new() )) } #[test] fn failing_seqs() { let left_failing_node = Box::new(ParserNode { expr: ParserExpr::Str("i'm a failure".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_failing( &left_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let left_non_failing_node = Box::new(ParserNode { expr: ParserExpr::Str("".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_failing( &left_non_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_failing_node = Box::new(ParserNode { expr: ParserExpr::Ident("DROP".into()), span: Span::new("DROP", 0, 3).unwrap(), }); assert!(!is_non_failing( &right_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let right_non_failing_node = Box::new(ParserNode { expr: ParserExpr::Opt(Box::new(ParserNode { expr: ParserExpr::Str(" ".into()), span: Span::new(" ", 0, 1).unwrap(), })), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_failing( &right_non_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::Seq(left_non_failing_node, right_failing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::Seq(left_failing_node.clone(), right_non_failing_node), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::Seq(left_failing_node, right_failing_node), &HashMap::new(), &mut Vec::new() )) } #[test] fn failing_range() { let failing = ParserExpr::Range("A".into(), "Z".into()); let always_failing = ParserExpr::Range("Z".into(), "A".into()); assert!(!is_non_failing(&failing, &HashMap::new(), &mut Vec::new())); assert!(!is_non_failing( &always_failing, &HashMap::new(), &mut Vec::new() )); } #[test] fn _push_node_tag_pos_pred_forwarding_is_non_failing() { let failing_node = Box::new(ParserNode { expr: ParserExpr::Str("i'm a failure".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(!is_non_failing( &failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); let non_failing_node = Box::new(ParserNode { expr: ParserExpr::Str("".into()), span: Span::new(" ", 0, 1).unwrap(), }); assert!(is_non_failing( &non_failing_node.clone().expr, &HashMap::new(), &mut Vec::new() )); #[cfg(feature = "grammar-extras")] { assert!(!is_non_failing( &ParserExpr::NodeTag(failing_node.clone(), "TAG".into()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::NodeTag(non_failing_node.clone(), "TAG".into()), &HashMap::new(), &mut Vec::new() )); } assert!(!is_non_failing( &ParserExpr::Push(failing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::Push(non_failing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(!is_non_failing( &ParserExpr::PosPred(failing_node.clone()), &HashMap::new(), &mut Vec::new() )); assert!(is_non_failing( &ParserExpr::PosPred(non_failing_node.clone()), &HashMap::new(), &mut Vec::new() )); } #[test] #[should_panic(expected = "grammar error --> 1:7 | 1 | a = { (\"\")* } | ^---^ | = expression inside repetition cannot fail and will repeat infinitely")] fn non_failing_repetition() { let input = "a = { (\"\")* }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:18 | 1 | a = { \"\" } b = { a* } | ^^ | = expression inside repetition cannot fail and will repeat infinitely")] fn indirect_non_failing_repetition() { let input = "a = { \"\" } b = { a* }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:20 | 1 | a = { \"a\" ~ (\"b\" ~ (\"\")*) } | ^---^ | = expression inside repetition cannot fail and will repeat infinitely")] fn deep_non_failing_repetition() { let input = "a = { \"a\" ~ (\"b\" ~ (\"\")*) }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:7 | 1 | a = { (\"\" ~ &\"a\" ~ !\"a\" ~ (SOI | EOI))* } | ^-------------------------------^ | = expression inside repetition is non-progressing and will repeat infinitely")] fn non_progressing_repetition() { let input = "a = { (\"\" ~ &\"a\" ~ !\"a\" ~ (SOI | EOI))* }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:20 | 1 | a = { !\"a\" } b = { a* } | ^^ | = expression inside repetition is non-progressing and will repeat infinitely")] fn indirect_non_progressing_repetition() { let input = "a = { !\"a\" } b = { a* }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:7 | 1 | a = { a } | ^ | = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] fn simple_left_recursion() { let input = "a = { a }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:7 | 1 | a = { b } b = { a } | ^ | = rule b is left-recursive (b -> a -> b); pest::pratt_parser might be useful in this case --> 1:17 | 1 | a = { b } b = { a } | ^ | = rule a is left-recursive (a -> b -> a); pest::pratt_parser might be useful in this case")] fn indirect_left_recursion() { let input = "a = { b } b = { a }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:39 | 1 | a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"\") ~ a } | ^ | = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] fn non_failing_left_recursion() { let input = "a = { \"\" ~ \"a\"? ~ \"a\"* ~ (\"a\" | \"\") ~ a }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:13 | 1 | a = { \"a\" | a } | ^ | = rule a is left-recursive (a -> a); pest::pratt_parser might be useful in this case")] fn non_primary_choice_left_recursion() { let input = "a = { \"a\" | a }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:7 | 1 | a = { \"a\"* | \"a\" | \"b\" } | ^--^ | = expression cannot fail; following choices cannot be reached")] fn lhs_non_failing_choice() { let input = "a = { \"a\"* | \"a\" | \"b\" }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:13 | 1 | a = { \"a\" | \"a\"* | \"b\" } | ^--^ | = expression cannot fail; following choices cannot be reached")] fn lhs_non_failing_choice_middle() { let input = "a = { \"a\" | \"a\"* | \"b\" }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] #[should_panic(expected = "grammar error --> 1:7 | 1 | a = { b | \"a\" } b = { \"b\"* | \"c\" } | ^ | = expression cannot fail; following choices cannot be reached --> 1:23 | 1 | a = { b | \"a\" } b = { \"b\"* | \"c\" } | ^--^ | = expression cannot fail; following choices cannot be reached")] fn lhs_non_failing_nested_choices() { let input = "a = { b | \"a\" } b = { \"b\"* | \"c\" }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } #[test] fn skip_can_be_defined() { let input = "skip = { \"\" }"; unwrap_or_report(consume_rules( PestParser::parse(Rule::grammar_rules, input).unwrap(), )); } }