abnf-0.13.0/.cargo_vcs_info.json0000644000000001360000000000100120230ustar { "git": { "sha1": "98f7356bdf47d2f7dc0ac1cbe6d9178212ca2309" }, "path_in_vcs": "" }abnf-0.13.0/.github/workflows/ci.yml000064400000000000000000000036171046102023000153350ustar 00000000000000name: CI on: push: branches: [ main ] paths: - '**.rs' - '**.toml' - '.github/workflows/**' pull_request: branches: [ main ] paths: - '**.rs' - '**.toml' - '.github/workflows/**' env: CARGO_TERM_COLOR: always jobs: test: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable] runs-on: ${{ matrix.os }} steps: - name: Install toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - name: Checkout code uses: actions/checkout@v2 - name: Build code uses: actions-rs/cargo@v1 with: command: build args: --all --all-features - name: Test code uses: actions-rs/cargo@v1 with: command: test args: --all --all-features audit: needs: test runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Audit dependencies uses: EmbarkStudios/cargo-deny-action@v1 lint: needs: test runs-on: ubuntu-latest steps: - name: Install toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true components: clippy - name: Checkout code uses: actions/checkout@v2 - name: Check code formatting uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check --config imports_granularity=Crate,group_imports=StdExternalCrate - name: Check for common mistakes and missed improvements uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --all-features abnf-0.13.0/.github/workflows/scheduled.yml000064400000000000000000000003021046102023000166660ustar 00000000000000name: Scheduled on: schedule: - cron: '0 0 * * *' jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: EmbarkStudios/cargo-deny-action@v1 abnf-0.13.0/.gitignore000064400000000000000000000001421046102023000126000ustar 00000000000000/target /Cargo.lock /tmp # These are backup files generated by rustfmt **/*.rs.bk .vscode .idea abnf-0.13.0/CHANGELOG.md000064400000000000000000000013361046102023000124270ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. ## [Unreleased] - YYYY-MM-DD - ... ## [v0.13.0] - 2022-10-21 ### Added * GitHub Actions CI * Case-Sensitive String Support in ABNF ([RFC 7405](https://www.rfc-editor.org/rfc/rfc7405)) * Thanks, @timothee-haudebourg! ([#21](https://github.com/duesee/abnf/pull/21)) [Unreleased]: https://github.com/duesee/abnf/compare/v0.13.0...HEAD [v0.13.0]: https://github.com/duesee/abnf/compare/71b2a2a668a2a87846a1c138ce1b84ce17a119c4...v0.13.0 [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html abnf-0.13.0/Cargo.lock0000644000000141170000000000100100020ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "abnf" version = "0.13.0" dependencies = [ "abnf-core", "nom", "quickcheck", "quickcheck_macros", "rand", "rand_distr", ] [[package]] name = "abnf-core" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" dependencies = [ "nom", ] [[package]] name = "aho-corasick" version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "env_logger" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", ] [[package]] name = "getrandom" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "libc" version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "libm" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", "libm", ] [[package]] name = "ppv-lite86" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger", "log", "rand", ] [[package]] name = "quickcheck_macros" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "quote" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rand_distr" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", "rand", ] [[package]] name = "regex" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "syn" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" abnf-0.13.0/Cargo.toml0000644000000020220000000000100100150ustar # 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 = "2018" name = "abnf" version = "0.13.0" authors = ["Damian Poddebniak "] description = "A nom-based parser for ABNF." readme = "README.md" keywords = [ "abnf", "parser", "nom", ] license = "MIT OR Apache-2.0" repository = "https://github.com/duesee/abnf" [dependencies.abnf-core] version = "0.5" [dependencies.nom] version = "7" [dev-dependencies.quickcheck] version = "1" [dev-dependencies.quickcheck_macros] version = "1" [dev-dependencies.rand] version = "0.8" [dev-dependencies.rand_distr] version = "0.4" abnf-0.13.0/Cargo.toml.orig000064400000000000000000000007201046102023000135010ustar 00000000000000[package] name = "abnf" description = "A nom-based parser for ABNF." version = "0.13.0" authors = ["Damian Poddebniak "] readme = "README.md" license = "MIT OR Apache-2.0" edition = "2018" repository = "https://github.com/duesee/abnf" keywords = ["abnf", "parser", "nom"] [dependencies] nom = "7" abnf-core = "0.5" [dev-dependencies] quickcheck = "1" quickcheck_macros = "1" rand = "0.8" rand_distr = "0.4" abnf-0.13.0/LICENSE-APACHE000064400000000000000000000261351046102023000125460ustar 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. abnf-0.13.0/LICENSE-MIT000064400000000000000000000020621046102023000122470ustar 00000000000000MIT License Copyright (c) 2020 Damian Poddebniak Permission 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. abnf-0.13.0/README.md000064400000000000000000000040411046102023000120710ustar 00000000000000[![CI](https://github.com/duesee/abnf/actions/workflows/ci.yml/badge.svg)](https://github.com/duesee/abnf/actions/workflows/ci.yml) [![Scheduled](https://github.com/duesee/abnf/actions/workflows/scheduled.yml/badge.svg)](https://github.com/duesee/abnf/actions/workflows/scheduled.yml) [![docs](https://docs.rs/abnf/badge.svg)](https://docs.rs/abnf) # ABNF A parser for ABNF based on nom 7. ## Example The following code ```rust use abnf::rulelist; // Note: mind the trailing newline! match rulelist("a = b / c\nc = *(d e)\n") { Ok(rules) => println!("{:#?}", rules), Err(error) => eprintln!("{}", error), } ``` outputs ```rust [ Rule { name: "a", node: Alternatives( [ Rulename( "b", ), Rulename( "c", ), ], ), kind: Basic, }, Rule { name: "c", node: Repetition { repeat: Variable { min: None, max: None, }, node: Group( Concatenation( [ Rulename( "d", ), Rulename( "e", ), ], ), ), }, kind: Basic, }, ] ``` You can also use the provided example to parse and `Debug`-print any ABNF file. ```sh cargo run --example=example examples/assets/abnf.abnf ``` ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. abnf-0.13.0/deny.toml000064400000000000000000000002411046102023000124440ustar 00000000000000[bans] multiple-versions = "deny" [sources] unknown-registry = "deny" unknown-git = "deny" [licenses] allow = [ "Apache-2.0", "MIT", "Unicode-DFS-2016" ] abnf-0.13.0/examples/assets/abnf.abnf000064400000000000000000000041001046102023000154640ustar 00000000000000; errata id: 3076 rulelist = 1*( rule / (*WSP c-nl) ) rule = rulename defined-as elements c-nl ; continues if next line starts ; with white space rulename = ALPHA *(ALPHA / DIGIT / "-") defined-as = *c-wsp ("=" / "=/") *c-wsp ; basic rules definition and ; incremental alternatives ; errata id: 2968 elements = alternation *WSP c-wsp = WSP / (c-nl WSP) c-nl = comment / CRLF ; comment or newline comment = ";" *(WSP / VCHAR) CRLF alternation = concatenation *(*c-wsp "/" *c-wsp concatenation) concatenation = repetition *(1*c-wsp repetition) repetition = [repeat] element repeat = 1*DIGIT / (*DIGIT "*" *DIGIT) element = rulename / group / option / char-val / num-val / prose-val group = "(" *c-wsp alternation *c-wsp ")" option = "[" *c-wsp alternation *c-wsp "]" ; update rfc 7405 char-val = case-insensitive-string / case-sensitive-string case-insensitive-string = [ "%i" ] quoted-string case-sensitive-string = "%s" quoted-string quoted-string = DQUOTE *(%x20-21 / %x23-7E) DQUOTE ; quoted string of SP and VCHAR ; without DQUOTE num-val = "%" (bin-val / dec-val / hex-val) bin-val = "b" 1*BIT [ 1*("." 1*BIT) / ("-" 1*BIT) ] ; series of concatenated bit values ; or single ONEOF range dec-val = "d" 1*DIGIT [ 1*("." 1*DIGIT) / ("-" 1*DIGIT) ] hex-val = "x" 1*HEXDIG [ 1*("." 1*HEXDIG) / ("-" 1*HEXDIG) ] prose-val = "<" *(%x20-3D / %x3F-7E) ">" ; bracketed string of SP and VCHAR without angles ; prose description, to be used as last resort abnf-0.13.0/examples/assets/abnf_core.abnf000064400000000000000000000021361046102023000165030ustar 00000000000000ALPHA = %x41-5A / %x61-7A ; A-Z / a-z BIT = "0" / "1" CHAR = %x01-7F ; any 7-bit US-ASCII character, excluding NUL CR = %x0D ; carriage return CRLF = CR LF ; Internet standard newline CTL = %x00-1F / %x7F ; controls DIGIT = %x30-39 ; 0-9 DQUOTE = %x22 ; " (Double Quote) HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" HTAB = %x09 ; horizontal tab LF = %x0A ; linefeed LWSP = *(WSP / CRLF WSP) ; Use of this linear-white-space rule ; permits lines containing only white ; space that are no longer legal in ; mail headers and have caused ; interoperability problems in other ; contexts. ; Do not use when defining mail ; headers and use with caution in ; other contexts. OCTET = %x00-FF ; 8 bits of data SP = %x20 VCHAR = %x21-7E ; visible (printing) characters WSP = SP / HTAB ; white space abnf-0.13.0/examples/assets/imap.abnf000064400000000000000000000430421046102023000155140ustar 00000000000000address = "(" addr-name SP addr-adl SP addr-mailbox SP addr-host ")" addr-adl = nstring ; Holds route from [RFC-2822] route-addr if ; non-NIL addr-host = nstring ; NIL indicates [RFC-2822] group syntax. ; Otherwise, holds [RFC-2822] domain name addr-mailbox = nstring ; NIL indicates end of [RFC-2822] group; if ; non-NIL and addr-host is NIL, holds ; [RFC-2822] group name. ; Otherwise, holds [RFC-2822] local-part ; after removing [RFC-2822] quoting addr-name = nstring ; If non-NIL, holds phrase from [RFC-2822] ; mailbox after removing [RFC-2822] quoting append = "APPEND" SP mailbox [SP flag-list] [SP date-time] SP literal astring = 1*ASTRING-CHAR / string ASTRING-CHAR = ATOM-CHAR / resp-specials atom = 1*ATOM-CHAR ATOM-CHAR = %x21 / %x23-24 / %x26-27 / %x2B-5B / %x5E-7A / %x7C-7E ; mod: was atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials / resp-specials authenticate = "AUTHENTICATE" SP auth-type *(CRLF base64) auth-type = atom ; Defined by [SASL] base64 = *(4base64-char) [base64-terminal] base64-char = ALPHA / DIGIT / "+" / "/" ; Case-sensitive base64-terminal = (2base64-char "==") / (3base64-char "=") body = "(" (body-type-1part / body-type-mpart) ")" body-extension = nstring / number / "(" body-extension *(SP body-extension) ")" ; Future expansion. Client implementations ; MUST accept body-extension fields. Server ; implementations MUST NOT generate ; body-extension fields except as defined by ; future standard or standards-track ; revisions of this specification. body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang [SP body-fld-loc *(SP body-extension)]]] ; MUST NOT be returned on non-extensible ; "BODY" fetch body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang [SP body-fld-loc *(SP body-extension)]]] ; MUST NOT be returned on non-extensible ; "BODY" fetch body-fields = body-fld-param SP body-fld-id SP body-fld-desc SP body-fld-enc SP body-fld-octets body-fld-desc = nstring body-fld-dsp = "(" string SP body-fld-param ")" / nil body-fld-enc = (DQUOTE ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ "QUOTED-PRINTABLE") DQUOTE) / string body-fld-id = nstring body-fld-lang = nstring / "(" string *(SP string) ")" body-fld-loc = nstring body-fld-lines = number body-fld-md5 = nstring body-fld-octets = number body-fld-param = "(" string SP string *(SP string SP string) ")" / nil body-type-1part = (body-type-basic / body-type-msg / body-type-text) [SP body-ext-1part] body-type-basic = media-basic SP body-fields ; MESSAGE subtype MUST NOT be "RFC822" body-type-mpart = 1*body SP media-subtype [SP body-ext-mpart] body-type-msg = media-message SP body-fields SP envelope SP body SP body-fld-lines body-type-text = media-text SP body-fields SP body-fld-lines capability = ("AUTH=" auth-type) / atom ; New capabilities MUST begin with "X" or be ; registered with IANA as standard or ; standards-track capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1" *(SP capability) ; Servers MUST implement the STARTTLS, AUTH=PLAIN, ; and LOGINDISABLED capabilities ; Servers which offer RFC 1730 compatibility MUST ; list "IMAP4" as the first capability. CHAR8 = %x01-ff ; any OCTET except NUL, %x00 ; errata id: 261 charset = atom / quoted command = tag SP (command-any / command-auth / command-nonauth / command-select) CRLF ; Modal based on state command-any = "CAPABILITY" / "LOGOUT" / "NOOP" / x-command ; Valid in all states command-auth = append / create / delete / examine / list / lsub / rename / select / status / subscribe / unsubscribe ; Valid only in Authenticated or Selected state command-nonauth = login / authenticate / "STARTTLS" ; Valid only when in Not Authenticated state command-select = "CHECK" / "CLOSE" / "EXPUNGE" / copy / fetch / store / uid / search ; Valid only when in Selected state continue-req = "+" SP (resp-text / base64) CRLF copy = "COPY" SP sequence-set SP mailbox create = "CREATE" SP mailbox ; Use of INBOX gives a NO error date = date-text / DQUOTE date-text DQUOTE date-day = 1*2DIGIT ; Day of month date-day-fixed = (SP DIGIT) / 2DIGIT ; Fixed-format version of date-day date-month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" date-text = date-day "-" date-month "-" date-year date-year = 4DIGIT date-time = DQUOTE date-day-fixed "-" date-month "-" date-year SP time SP zone DQUOTE delete = "DELETE" SP mailbox ; Use of INBOX gives a NO error digit-nz = %x31-39 ; 1-9 envelope = "(" env-date SP env-subject SP env-from SP env-sender SP env-reply-to SP env-to SP env-cc SP env-bcc SP env-in-reply-to SP env-message-id ")" env-bcc = "(" 1*address ")" / nil env-cc = "(" 1*address ")" / nil env-date = nstring env-from = "(" 1*address ")" / nil env-in-reply-to = nstring env-message-id = nstring env-reply-to = "(" 1*address ")" / nil env-sender = "(" 1*address ")" / nil env-subject = nstring env-to = "(" 1*address ")" / nil examine = "EXAMINE" SP mailbox fetch = "FETCH" SP sequence-set SP ("ALL" / "FULL" / "FAST" / fetch-att / "(" fetch-att *(SP fetch-att) ")") fetch-att = "ENVELOPE" / "FLAGS" / "INTERNALDATE" / "RFC822" [".HEADER" / ".SIZE" / ".TEXT"] / "BODY" ["STRUCTURE"] / "UID" / "BODY" section ["<" number "." nz-number ">"] / "BODY.PEEK" section ["<" number "." nz-number ">"] flag = "\Answered" / "\Flagged" / "\Deleted" / "\Seen" / "\Draft" / flag-keyword / flag-extension ; Does not include "\Recent" flag-extension = "\" atom ; Future expansion. Client implementations ; MUST accept flag-extension flags. Server ; implementations MUST NOT generate ; flag-extension flags except as defined by ; future standard or standards-track ; revisions of this specification. flag-fetch = flag / "\Recent" flag-keyword = atom flag-list = "(" [flag *(SP flag)] ")" flag-perm = flag / "\*" greeting = "*" SP (resp-cond-auth / resp-cond-bye) CRLF header-fld-name = astring header-list = "(" header-fld-name *(SP header-fld-name) ")" list = "LIST" SP mailbox SP list-mailbox list-mailbox = 1*list-char / string list-char = ATOM-CHAR / list-wildcards / resp-specials list-wildcards = "%" / "*" literal = "{" number "}" CRLF *CHAR8 ; Number represents the number of CHAR8s login = "LOGIN" SP userid SP password lsub = "LSUB" SP mailbox SP list-mailbox mailbox = "INBOX" / astring ; INBOX is case-insensitive. All case variants of ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX ; not as an astring. An astring which consists of ; the case-insensitive sequence "I" "N" "B" "O" "X" ; is considered to be INBOX and not an astring. ; Refer to section 5.1 for further ; semantic details of mailbox names. mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list / "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) / "STATUS" SP mailbox SP "(" [status-att-list] ")" / number SP "EXISTS" / number SP "RECENT" mailbox-list = "(" [mbx-list-flags] ")" SP (DQUOTE QUOTED-CHAR DQUOTE / nil) SP mailbox mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag *(SP mbx-list-oflag) / mbx-list-oflag *(SP mbx-list-oflag) mbx-list-oflag = "\Noinferiors" / flag-extension ; Other flags; multiple possible per LIST response mbx-list-sflag = "\Noselect" / "\Marked" / "\Unmarked" ; Selectability flags; only one per LIST response media-basic = ((DQUOTE ("APPLICATION" / "AUDIO" / "IMAGE" / "MESSAGE" / "VIDEO") DQUOTE) / string) SP media-subtype ; Defined in [MIME-IMT] media-message = DQUOTE "MESSAGE" DQUOTE SP DQUOTE "RFC822" DQUOTE ; Defined in [MIME-IMT] media-subtype = string ; Defined in [MIME-IMT] media-text = DQUOTE "TEXT" DQUOTE SP media-subtype ; Defined in [MIME-IMT] message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att)) msg-att = "(" (msg-att-dynamic / msg-att-static) *(SP (msg-att-dynamic / msg-att-static)) ")" msg-att-dynamic = "FLAGS" SP "(" [flag-fetch *(SP flag-fetch)] ")" ; MAY change for a message msg-att-static = "ENVELOPE" SP envelope / "INTERNALDATE" SP date-time / "RFC822" [".HEADER" / ".TEXT"] SP nstring / "RFC822.SIZE" SP number / "BODY" ["STRUCTURE"] SP body / "BODY" section ["<" number ">"] SP nstring / "UID" SP uniqueid ; MUST NOT change for a message nil = "NIL" nstring = string / nil number = 1*DIGIT ; Unsigned 32-bit integer ; (0 <= n < 4,294,967,296) nz-number = digit-nz *DIGIT ; Non-zero unsigned 32-bit integer ; (0 < n < 4,294,967,296) password = astring quoted = DQUOTE *QUOTED-CHAR DQUOTE QUOTED-CHAR = (%x01-09 / %x0B-0C / %x0E-21 / %x23-5B / %x5D-7F) / "\" quoted-specials ; mod: was / "\" quoted-specials quoted-specials = DQUOTE / "\" rename = "RENAME" SP mailbox SP mailbox ; Use of INBOX as a destination gives a NO error response = *(continue-req / response-data) response-done response-data = "*" SP (resp-cond-state / resp-cond-bye / mailbox-data / message-data / capability-data) CRLF response-done = response-tagged / response-fatal response-fatal = "*" SP resp-cond-bye CRLF ; Server closes connection immediately response-tagged = tag SP resp-cond-state CRLF resp-cond-auth = ("OK" / "PREAUTH") SP resp-text ; Authentication condition resp-cond-bye = "BYE" SP resp-text resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text ; Status condition resp-specials = "]" resp-text = ["[" resp-text-code "]" SP] text ; errata id: 261 resp-text-code = "ALERT" / "BADCHARSET" [SP "(" charset *(SP charset) ")" ] / capability-data / "PARSE" / "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" / "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number / "UNSEEN" SP nz-number / atom [SP 1*(%x01-09 / %x0B-0C / %x0E-5C / %x5E-7F)] ; mod: was atom [SP 1*] ; errata id: 261 search = "SEARCH" [SP "CHARSET" SP charset] 1*(SP search-key) ; CHARSET argument to MUST be registered with IANA search-key = "ALL" / "ANSWERED" / "BCC" SP astring / "BEFORE" SP date / "BODY" SP astring / "CC" SP astring / "DELETED" / "FLAGGED" / "FROM" SP astring / "KEYWORD" SP flag-keyword / "NEW" / "OLD" / "ON" SP date / "RECENT" / "SEEN" / "SINCE" SP date / "SUBJECT" SP astring / "TEXT" SP astring / "TO" SP astring / "UNANSWERED" / "UNDELETED" / "UNFLAGGED" / "UNKEYWORD" SP flag-keyword / "UNSEEN" / ; Above this line were in [IMAP2] "DRAFT" / "HEADER" SP header-fld-name SP astring / "LARGER" SP number / "NOT" SP search-key / "OR" SP search-key SP search-key / "SENTBEFORE" SP date / "SENTON" SP date / "SENTSINCE" SP date / "SMALLER" SP number / "UID" SP sequence-set / "UNDRAFT" / sequence-set / "(" search-key *(SP search-key) ")" section = "[" [section-spec] "]" section-msgtext = "HEADER" / "HEADER.FIELDS" [".NOT"] SP header-list / "TEXT" ; top-level or MESSAGE/RFC822 part section-part = nz-number *("." nz-number) ; body part nesting section-spec = section-msgtext / (section-part ["." section-text]) section-text = section-msgtext / "MIME" ; text other than actual body part (headers, etc.) select = "SELECT" SP mailbox seq-number = nz-number / "*" ; message sequence number (COPY, FETCH, STORE ; commands) or unique identifier (UID COPY, ; UID FETCH, UID STORE commands). ; * represents the largest number in use. In ; the case of message sequence numbers, it is ; the number of messages in a non-empty mailbox. ; In the case of unique identifiers, it is the ; unique identifier of the last message in the ; mailbox or, if the mailbox is empty, the ; mailbox's current UIDNEXT value. ; The server should respond with a tagged BAD ; response to a command that uses a message ; sequence number greater than the number of ; messages in the selected mailbox. This ; includes "*" if the selected mailbox is empty. seq-range = seq-number ":" seq-number ; two seq-number values and all values between ; these two regardless of order. ; Example: 2:4 and 4:2 are equivalent and indicate ; values 2, 3, and 4. ; Example: a unique identifier sequence range of ; 3291:* includes the UID of the last message in ; the mailbox, even if that value is less than 3291. ; errata id: 261 sequence-set = (seq-number / seq-range) ["," sequence-set] ; set of seq-number values, regardless of order. ; Servers MAY coalesce overlaps and/or execute the ; sequence in any order. ; Example: a message sequence number set of ; 2,4:7,9,12:* for a mailbox with 15 messages is ; equivalent to 2,4,5,6,7,9,12,13,14,15 ; Example: a message sequence number set of *:4,5:7 ; for a mailbox with 10 messages is equivalent to ; 10,9,8,7,6,5,4,5,6,7 and MAY be reordered and ; overlap coalesced to be 4,5,6,7,8,9,10. status = "STATUS" SP mailbox SP "(" status-att *(SP status-att) ")" status-att = "MESSAGES" / "RECENT" / "UIDNEXT" / "UIDVALIDITY" / "UNSEEN" ; errata id: 261 status-att-val = ("MESSAGES" SP number) / ("RECENT" SP number) / ("UIDNEXT" SP nz-number) / ("UIDVALIDITY" SP nz-number) / ("UNSEEN" SP number) ; errata id: 261 status-att-list = status-att-val *(SP status-att-val) store = "STORE" SP sequence-set SP store-att-flags store-att-flags = (["+" / "-"] "FLAGS" [".SILENT"]) SP (flag-list / (flag *(SP flag))) string = quoted / literal subscribe = "SUBSCRIBE" SP mailbox tag = 1*(%x21 / %x23-24 / %x26-27 / %x2C-5B / %x5D-7A / %x7C-7E) ; mod: was 1* text = 1*TEXT-CHAR TEXT-CHAR = %x01-09 / %x0B-0C / %x0E-7F ; mod: was time = 2DIGIT ":" 2DIGIT ":" 2DIGIT ; Hours minutes seconds uid = "UID" SP (copy / fetch / search / store) ; Unique identifiers used instead of message ; sequence numbers uniqueid = nz-number ; Strictly ascending unsubscribe = "UNSUBSCRIBE" SP mailbox userid = astring x-command = "X" atom zone = ("+" / "-") 4DIGIT ; Signed four-digit value of hhmm representing ; hours and minutes east of Greenwich (that is, ; the amount that the given time differs from ; Universal Time). Subtracting the timezone ; from the given time will give the UT form. ; The Universal Time zone is "+0000". abnf-0.13.0/examples/assets/test.abnf000064400000000000000000000000311046102023000155340ustar 00000000000000a = b / (c d) d = *(e f) abnf-0.13.0/examples/dependencies.rs000064400000000000000000000064341046102023000154340ustar 00000000000000//! This example shows how to use the `abnf` crate to create a dependency graph of `Rule`s. use std::env::args; use abnf::{ rulelist, types::{Node, Rule}, }; /// A type which implements this trait is able to report on what rules it "depends" on. /// For simplicity, the dependencies are just a vector of strings here. trait Dependencies { /// Obtain a list of all rulenames. fn calc_dependencies(&self) -> Vec; } impl Dependencies for Rule { fn calc_dependencies(&self) -> Vec { self.node().calc_dependencies() } } impl Dependencies for Node { fn calc_dependencies(&self) -> Vec { match self { // If we are a set of alternatives or a concatenation, // collect the dependencies of all alternatives/concatenated elements. Node::Alternatives(nodes) | Node::Concatenation(nodes) => { let mut ret_val = Vec::new(); for node in nodes { for dep in node.calc_dependencies() { if !ret_val.contains(&dep) { ret_val.push(dep); } } } ret_val } Node::Group(node) | Node::Optional(node) => node.calc_dependencies(), Node::Repetition { node, .. } => node.calc_dependencies(), Node::Rulename(name) => vec![name.to_owned()], Node::TerminalValues(_) | Node::String(_) | Node::Prose(_) => vec![], } } } fn print_gml(rules: Vec) { println!("graph ["); for rule in rules.iter() { println!("\tnode [id \"{}\" label \"{}\"]", rule.name(), rule.name()); } for rule in rules.iter() { for dep in rule.calc_dependencies() { if [ "ALPHA", "BIT", "CHAR", "CR", "CRLF", "CTL", "DIGIT", "DQUOTE", "HEXDIG", "HTAB", "LF", "LWSP", "OCTET", "SP", "VCHAR", "WSP", ] .contains(&&dep[..]) { continue; } println!("\tedge [source \"{}\" target \"{}\"]", rule.name(), dep); } } println!("]"); } fn print_gv(rules: Vec) { println!("digraph {{"); println!("\tcompound=true;"); println!("\toverlap=scalexy;"); println!("\tsplines=true;"); println!("\tlayout=neato;\n"); for rule in rules.iter() { let name = rule.name().to_owned().replace("-", "_"); let deps = rule .calc_dependencies() .iter() .map(|name| name.replace("-", "_")) .collect::>(); println!("\t{} -> {{{}}}", name, deps.join(" ")); } println!("}}"); } fn main() -> std::io::Result<()> { let rules = { let data = std::fs::read_to_string(args().nth(1).expect("USAGE: dependencies file [gml|gv]"))?; rulelist(&data).unwrap_or_else(|e| { println!("{}", e); std::process::exit(1); }) }; let format = args().nth(2).unwrap_or(String::from("gml")); match format.as_ref() { "gml" => print_gml(rules), "gv" => print_gv(rules), other => { eprintln!("Unknown format \"{}\". Try \"gml\" or \"gv\".", other); std::process::exit(1); } } Ok(()) } abnf-0.13.0/examples/example.rs000064400000000000000000000005741046102023000144400ustar 00000000000000use std::error::Error; use abnf::rulelist; fn main() -> Result<(), Box> { let rules = { let path = std::env::args().nth(1).ok_or("No path to file given.")?; let data = std::fs::read_to_string(path)?; rulelist(&data)? }; for rule in &rules { println!("// {}", rule); println!("{:#?}\n", rule); } Ok(()) } abnf-0.13.0/src/error.rs000064400000000000000000000020551046102023000131030ustar 00000000000000//! This module contains error related structs. //! //! Currently, this module defines `ParsingError`, whos only purpose is to be displayed to a user. //! //! # Example //! //! This code ... //! //! ``` //! use abnf::rule; //! //! let error = rule("bad-rule = *a]").unwrap_err(); //! //! println!("{}", error); //! ``` //! //! ... will print ... //! //! ```text //! 0: at line 0, in Tag: //! bad-rule = *a] //! ^ //! //! 1: at line 0, in Alt: //! bad-rule = *a] //! ^ //! //! 2: at line 0, in Alt: //! bad-rule = *a] //! ^ //! ``` //! //! **Note**: `ParsingError` is in fact just `Nom`'s `VerboseError` in disguise. //! Currently, it is a best effort solution to give a rough idea where the erroneous syntax is. use std::{error::Error, fmt}; /// A generic parsing error. #[derive(Debug)] pub struct ParseError { pub(crate) message: String, } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "{}", self.message) } } impl Error for ParseError {} abnf-0.13.0/src/lib.rs000064400000000000000000000710051046102023000125210ustar 00000000000000#![warn(missing_docs)] //! A crate for parsing ABNF definitions ([RFC5234](https://tools.ietf.org/html/rfc5234)) //! //! Two functions are exposed for now, [rulelist](fn.rulelist.html) and [rule](fn.rule.html). //! They cover the use case, where the complete ABNF definition is provided as a string. //! //! On success, both functions return `Ok(...)` with the parsed object. //! If the ABNF definition contains a syntax error, both functions return `Err(ParsingError)`. //! `ParsingError` is intended to be `Display`ed to a user and should help correcting mistakes in the //! provided ABNF definition. //! //! It is also possible to create rules manually as showed in the example in the [types](types/index.html) module. //! //! # Example //! //! ``` //! use abnf::rulelist; //! //! // Note: mind the trailing newline! //! match rulelist("a = b\nc = *d\n") { //! Ok(rules) => { //! for rule in &rules { //! println!("{:#?}\n", rule); //! } //! }, //! Err(error) => eprintln!("{}", error), //! } //! ``` use abnf_core::{complete::*, is_ALPHA, is_BIT, is_DIGIT, is_HEXDIG}; use nom::{ branch::alt, bytes::complete::{tag, take_until, take_while, take_while1}, character::complete::char, combinator::{all_consuming, map, opt, recognize, value}, error::{convert_error, ErrorKind, ParseError, VerboseError}, multi::{many0, many1, separated_list1}, sequence::{delimited, preceded, separated_pair, terminated, tuple}, IResult, }; use crate::types::*; pub mod error; pub mod types; /// Parses a list of multiple ABNF rules. /// Returns `Ok(Vec)` when everything went well and `Err(ParsingError)` in case of syntax errors. /// /// **Note**: `input` must end with a newline and whitespace must not appear before the rulename. /// (This may be relaxed in the future.) /// /// # Example /// /// ``` /// use abnf::rulelist; /// /// // Note: mind the trailing newline! /// match rulelist("a = b\nc = *d\n") { /// Ok(rules) => println!("{:#?}", rules), /// Err(error) => eprintln!("{}", error), /// } /// ``` pub fn rulelist(input: &str) -> Result, crate::error::ParseError> { match all_consuming(rulelist_internal::>)(input) { Ok((remaining, rules)) => { assert!(remaining.is_empty()); Ok(rules) } Err(error) => match error { nom::Err::Incomplete(_) => unreachable!(), nom::Err::Error(e) | nom::Err::Failure(e) => Err(crate::error::ParseError { message: convert_error(input, e), }), }, } } /// Parses a single ABNF rule. /// Returns `Ok(Rule)` when everything went well and `Err(ParsingError)` in case of syntax errors. /// /// **Note**: `input` must end with a newline and whitespace must not appear before the rulename. /// (This may be relaxed in the future.) /// /// # Example /// /// ``` /// use abnf::rule; /// /// match rule("a = b / c / *d") { /// Ok(rules) => println!("{:#?}", rules), /// Err(error) => eprintln!("{}", error), /// } /// ``` pub fn rule(input: &str) -> Result { match all_consuming(rule_internal_single::>)(input) { Ok((remaining, rule)) => { assert!(remaining.is_empty()); Ok(rule) } Err(error) => match error { nom::Err::Incomplete(_) => unreachable!(), nom::Err::Error(e) | nom::Err::Failure(e) => Err(crate::error::ParseError { message: convert_error(input, e), }), }, } } // ------------------------------------------------------------------------------------------------- /// ```abnf /// rulelist = 1*( rule / (*WSP c-nl) ) /// ; Errata ID: 3076 /// ``` fn rulelist_internal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Vec, E> { let mut parser = many1(alt(( map(rule_internal, Some), map(tuple((many0(WSP), c_nl)), |_| None), ))); let (input, rulelist) = parser(input)?; Ok((input, rulelist.into_iter().flatten().collect())) } /// ```abnf /// rule = rulename defined-as elements c-nl /// ; continues if next line starts /// ; with white space /// ``` fn rule_internal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, Rule, E> { let mut parser = tuple((rulename, defined_as, elements, c_nl)); let (input, (name, definition, elements, _)) = parser(input)?; let rule = match definition { Kind::Basic => Rule::new(&name, elements), Kind::Incremental => Rule::incremental(&name, elements), }; Ok((input, rule)) } /// Whitespace and comments before rule allowed, no trailing newline required. fn rule_internal_single<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, Rule, E> { let mut parser = tuple(( many0(alt((c_nl, recognize(WSP)))), rulename, defined_as, elements, opt(c_nl), )); let (input, (_, name, definition, elements, _)) = parser(input)?; let rule = match definition { Kind::Basic => Rule::new(&name, elements), Kind::Incremental => Rule::incremental(&name, elements), }; Ok((input, rule)) } /// ```abnf /// rulename = ALPHA *(ALPHA / DIGIT / "-") /// ``` fn rulename<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &str, E> { let is_valid = |x| is_ALPHA(x) || is_DIGIT(x) || x == '-'; recognize(tuple((ALPHA, take_while(is_valid))))(input) } /// Basic rules definition and incremental alternatives. /// /// ```abnf /// defined-as = *c-wsp ("=" / "=/") *c-wsp /// ``` fn defined_as<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Kind, E> { delimited( many0(c_wsp), alt(( value(Kind::Incremental, tag("=/")), value(Kind::Basic, tag("=")), )), many0(c_wsp), )(input) } /// ```abnf /// elements = alternation *WSP /// ; Errata ID: 2968 /// ``` fn elements<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> { terminated(alternation, many0(WSP))(input) } /// ```abnf /// alternation = concatenation *(*c-wsp "/" *c-wsp concatenation) /// ``` fn alternation<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> { let separator = tuple((many0(c_wsp), char('/'), many0(c_wsp))); let (input, mut concatenations) = separated_list1(separator, concatenation)(input)?; // if there is only a single element in the alternatives, do not wrap it in a `Node::Alternatives`. if concatenations.len() == 1 { Ok((input, concatenations.pop().unwrap())) } else { Ok((input, Node::Alternatives(concatenations))) } } /// ```abnf /// concatenation = repetition *(1*c-wsp repetition) /// ``` fn concatenation<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> { let separator = many1(c_wsp); let (input, mut repetitions) = separated_list1(separator, repetition)(input)?; // if there is only a single element in the concatenation, do not wrap it in a `Node::Concatenation`. if repetitions.len() == 1 { Ok((input, repetitions.pop().unwrap())) } else { Ok((input, Node::Concatenation(repetitions))) } } /// ```abnf /// repetition = [repeat] element /// ``` fn repetition<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> { let mut parser = tuple((opt(repeat), element)); let (input, (repeat, node)) = parser(input)?; // if there is no repeat, do not wrap it in a `Node::Repetition`. if let Some(repeat) = repeat { Ok((input, Node::repetition(repeat, node))) } else { Ok((input, node)) } } /// ```abnf /// repeat = 1*DIGIT / (*DIGIT "*" *DIGIT) /// ``` fn repeat<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Repeat, E> { alt(( map( separated_pair(opt(dec_usize), char('*'), opt(dec_usize)), |(min, max)| Repeat::Variable { min, max }, ), map(dec_usize, Repeat::Specific), ))(input) } /// ```abnf /// element = rulename / group / option / char-val / num-val / prose-val /// ``` fn element<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> { alt(( map(rulename, |rulename| Node::Rulename(rulename.to_owned())), group, option, map(char_val, Node::String), map(num_val, Node::TerminalValues), map(prose_val, |str| Node::Prose(str.to_owned())), ))(input) } /// ```abnf /// group = "(" *c-wsp alternation *c-wsp ")" /// ``` fn group<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> { let mut parser = delimited( char('('), delimited(many0(c_wsp), alternation, many0(c_wsp)), char(')'), ); let (input, alternation) = parser(input)?; Ok((input, Node::Group(Box::new(alternation)))) } /// ```abnf /// option = "[" *c-wsp alternation *c-wsp "]" /// ``` fn option<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Node, E> { let mut parser = delimited( char('['), delimited(many0(c_wsp), alternation, many0(c_wsp)), char(']'), ); let (input, alternation) = parser(input)?; Ok((input, Node::Optional(Box::new(alternation)))) } /// ```abnf /// char-val = case-insensitive-string / case-sensitive-string /// ``` fn char_val<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, StringLiteral, E> { alt(( map(case_insensitive_string, |str| { StringLiteral::case_insensitive(str.to_owned()) }), map(case_sensitive_string, |str| { StringLiteral::case_sensitive(str.to_owned()) }), ))(input) } /// ```abnf /// case-insensitive-string = [ "%i" ] quoted-string /// ``` fn case_insensitive_string<'a, E: ParseError<&'a str>>( input: &'a str, ) -> IResult<&'a str, &str, E> { let marker = preceded(char('%'), alt((char('i'), char('I')))); preceded(opt(marker), quoted_string)(input) } /// ```abnf /// case-sensitive-string = "%s" quoted-string /// ``` fn case_sensitive_string<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &str, E> { let marker = preceded(char('%'), alt((char('s'), char('S')))); preceded(marker, quoted_string)(input) } /// Quoted string of SP and VCHAR without DQUOTE /// /// ```abnf /// quoted-string = DQUOTE *(%x20-21 / %x23-7E) DQUOTE /// ``` fn quoted_string<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &str, E> { let is_inner = |x| matches!(x, '\x20'..='\x21' | '\x23'..='\x7E'); delimited(DQUOTE, take_while(is_inner), DQUOTE)(input) } /// ```abnf /// num-val = "%" (bin-val / dec-val / hex-val) /// ``` fn num_val<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, TerminalValues, E> { preceded(char('%'), alt((bin_val, dec_val, hex_val)))(input) } /// Series of concatenated bit values or single ONEOF range /// /// ```abnf /// bin-val = "b" 1*BIT [ 1*("." 1*BIT) / ("-" 1*BIT) ] /// ``` fn bin_val<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, TerminalValues, E> { preceded( char('b'), alt(( map( separated_pair(bin_u32, char('-'), bin_u32), |(start, end)| TerminalValues::Range(start, end), ), map( separated_list1(char('.'), bin_u32), TerminalValues::Concatenation, ), )), )(input) } /// ```abnf /// dec-val = "d" 1*DIGIT [ 1*("." 1*DIGIT) / ("-" 1*DIGIT) ] /// ``` fn dec_val<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, TerminalValues, E> { preceded( char('d'), alt(( map( separated_pair(dec_u32, char('-'), dec_u32), |(start, end)| TerminalValues::Range(start, end), ), map( separated_list1(char('.'), dec_u32), TerminalValues::Concatenation, ), )), )(input) } /// ```abnf /// hex-val = "x" 1*HEXDIG [ 1*("." 1*HEXDIG) / ("-" 1*HEXDIG) ] /// ``` fn hex_val<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, TerminalValues, E> { preceded( char('x'), alt(( map( separated_pair(hex_u32, char('-'), hex_u32), |(start, end)| TerminalValues::Range(start, end), ), map( separated_list1(char('.'), hex_u32), TerminalValues::Concatenation, ), )), )(input) } /// Bracketed string of SP and VCHAR without angles prose description, to be used as last resort. /// /// ```abnf /// prose-val = "<" *(%x20-3D / %x3F-7E) ">" /// ``` fn prose_val<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &str, E> { let is_inner = |x| matches!(x, '\x20'..='\x3D' | '\x3F'..='\x7E'); delimited(char('<'), take_while(is_inner), char('>'))(input) } /// Comment or Newline. /// /// ```abnf /// c-nl = comment / CRLF /// ``` fn c_nl<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { alt((comment, crlf_relaxed))(input) } /// ```abnf /// comment = ";" *(WSP / VCHAR) CRLF /// /// Relaxed, see . /// ``` fn comment<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { recognize(tuple((char(';'), take_until("\n"), char('\n'))))(input) } /// ```abnf /// c-wsp = WSP / (c-nl WSP) /// ``` fn c_wsp<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { alt((recognize(WSP), recognize(tuple((c_nl, recognize(WSP))))))(input) } // ------------------------------------------------------------------------------------------------- fn bin_u32<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, u32, E> { let (remaining, out) = take_while1(is_BIT)(input)?; match u32::from_str_radix(out, 2) { Ok(num) => Ok((remaining, num)), Err(_) => Err(nom::Err::Failure(nom::error::make_error( // FIXME: use error input, ErrorKind::Verify, ))), } } fn dec_u32<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, u32, E> { let (remaining, out) = take_while1(is_DIGIT)(input)?; match u32::from_str_radix(out, 10) { Ok(num) => Ok((remaining, num)), Err(_) => Err(nom::Err::Failure(nom::error::make_error( // FIXME: use error input, ErrorKind::Verify, ))), } } fn dec_usize<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, usize, E> { let (remaining, out) = take_while1(is_DIGIT)(input)?; match usize::from_str_radix(out, 10) { Ok(num) => Ok((remaining, num)), Err(_) => Err(nom::Err::Failure(nom::error::make_error( // FIXME: use error input, ErrorKind::Verify, ))), } } fn hex_u32<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, u32, E> { let (remaining, out) = take_while1(is_HEXDIG)(input)?; match u32::from_str_radix(out, 16) { Ok(num) => Ok((remaining, num)), Err(_) => Err(nom::Err::Failure(nom::error::make_error( // FIXME: use error input, ErrorKind::Verify, ))), } } #[cfg(test)] mod tests { use nom::error::VerboseError; use quickcheck::{Arbitrary, Gen}; use quickcheck_macros::quickcheck; use rand::{distributions::Distribution, seq::SliceRandom, thread_rng, Rng}; use super::*; struct RulenameDistribution; impl Distribution for RulenameDistribution { fn sample(&self, rng: &mut R) -> char { *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-" .choose(rng) .unwrap() as char } } /// Uniform char distribution in the set `%x20-21 / %x23-7E`. struct StringDistribution; impl Distribution for StringDistribution { fn sample(&self, rng: &mut R) -> char { let mut i = rng.gen_range(0x20u32..=0x7d); if i > 0x21 { // avoid %x22 i += 0x01 } char::from_u32(i).unwrap() } } impl Arbitrary for Rule { fn arbitrary(g: &mut Gen) -> Self { let rng = thread_rng(); let name: String = RulenameDistribution.sample_iter(rng).take(7).collect(); let name = String::from("a") + &name; match Kind::arbitrary(g) { Kind::Basic => Rule::new(&name, Node::arbitrary(g)), Kind::Incremental => Rule::incremental(&name, Node::arbitrary(g)), } } } impl Arbitrary for Node { fn arbitrary(g: &mut Gen) -> Self { let mut rng = thread_rng(); let name: String = RulenameDistribution.sample_iter(&mut rng).take(7).collect(); let name = String::from("a") + &name; match rng.gen_range(0..=8) { 0 => Node::Alternatives(vec![Node::arbitrary(g), Node::arbitrary(g)]), 1 => Node::Concatenation(vec![Node::arbitrary(g), Node::arbitrary(g)]), 2 => Node::Repetition { repeat: Repeat::arbitrary(g), node: Box::new(Node::arbitrary(g)), }, 3 => Node::Rulename(name), // TODO 4 => Node::Group(Box::::arbitrary(g)), 5 => Node::Optional(Box::::arbitrary(g)), 6 => Node::String(StringLiteral::arbitrary(g)), 7 => Node::TerminalValues(TerminalValues::arbitrary(g)), 8 => Node::Prose(name), // TODO _ => unreachable!(), } } } impl Arbitrary for StringLiteral { fn arbitrary(_: &mut Gen) -> Self { let mut rng = thread_rng(); let len = rand_distr::Binomial::new(20, 0.3).unwrap().sample(&mut rng) as usize; let value = StringDistribution.sample_iter(&mut rng).take(len).collect(); let case_sensitive = rand::distributions::Bernoulli::new(0.5) .unwrap() .sample(&mut rng); Self::new(value, case_sensitive) } } impl Arbitrary for Kind { fn arbitrary(_: &mut Gen) -> Self { match thread_rng().gen_range(0..=1) { 0 => Kind::Basic, 1 => Kind::Incremental, _ => unreachable!(), } } } impl Arbitrary for Repeat { fn arbitrary(g: &mut Gen) -> Self { match thread_rng().gen_range(0..=1) { 0 => Repeat::specific(::arbitrary(g)), 1 => Repeat::variable(Option::::arbitrary(g), Option::::arbitrary(g)), _ => unreachable!(), } } } impl Arbitrary for TerminalValues { fn arbitrary(g: &mut Gen) -> Self { match thread_rng().gen_range(0..=1) { 0 => TerminalValues::Concatenation(Vec::::arbitrary(g)), 1 => TerminalValues::Range(u32::arbitrary(g), u32::arbitrary(g)), _ => unreachable!(), } } } #[test] fn test_rules() { let tests = vec![ ("a = A\n", Rule::new("a", Node::rulename("A"))), ( "B = A / B\n", Rule::new( "B", Node::alternatives(&[Node::rulename("A"), Node::rulename("B")]), ), ), ( "c = (A / B)\n", Rule::new( "c", Node::group(Node::alternatives(&[ Node::rulename("A"), Node::rulename("B"), ])), ), ), ( "D = \n", Rule::new("D", Node::prose("this is prose")), ), ( "xXx = ((A B))\n", Rule::new( "xXx", Node::group(Node::group(Node::concatenation(&[ Node::rulename("A"), Node::rulename("B"), ]))), ), ), ( "a = 0*15\"-\"\n", Rule::new( "a", Node::repetition( Repeat::variable(Some(0), Some(15)), Node::string("-", false), ), ), ), ( "a = *\"-\"\n", Rule::new( "a", Node::repetition(Repeat::unbounded(), Node::string("-", false)), ), ), ]; for (test, expected) in tests { let (remaining, got) = rule_internal::>(test).unwrap(); assert!(remaining.is_empty()); assert_eq!(got, expected); } } #[test] fn test_rulename() { assert_eq!(rulename::>("a").unwrap().1, "a"); assert_eq!(rulename::>("A").unwrap().1, "A"); assert_eq!(rulename::>("ab").unwrap().1, "ab"); assert_eq!(rulename::>("Ab").unwrap().1, "Ab"); assert_eq!(rulename::>("A-b").unwrap().1, "A-b"); } #[test] fn test_alternation() { let (remaining, res) = alternation::>("A / \"xxx\"").unwrap(); assert!(remaining.len() == 0); println!("{:?}", res); } #[test] fn test_repetition() { let (remaining, res) = repetition::>("1*1A").unwrap(); assert!(remaining.len() == 0); println!("{:?}", res); } #[test] fn test_char_val() { rulelist("a = \"foo\"\n").unwrap(); rulelist("b = %i\"bar\"\n").unwrap(); rulelist("c = %s\"baz\"\n").unwrap(); rulelist("e = %I\"BAR\"\n").unwrap(); rulelist("f = %S\"BAZ\"\n").unwrap(); } #[test] fn test_num_val() { let expected = TerminalValues::Concatenation(vec![0x00, 0x0A, 0xff]); let got1 = num_val::>("%b0.1010.11111111"); let got2 = num_val::>("%d0.10.255"); let got3 = num_val::>("%x0.A.ff"); assert_eq!(expected, got1.unwrap().1); assert_eq!(expected, got2.unwrap().1); assert_eq!(expected, got3.unwrap().1); } #[test] fn test_bin_val() { let expected = TerminalValues::Concatenation(vec![0x00, 0x03, 0xff]); let got = bin_val::>("b00.11.11111111"); assert_eq!(expected, got.unwrap().1); let expected = TerminalValues::Range(0, 255); let got = bin_val::>("b00-11111111"); assert_eq!(expected, got.unwrap().1) } #[test] fn test_dec_val() { let expected = TerminalValues::Concatenation(vec![0, 42, 255]); let got = dec_val::>("d0.42.255"); assert_eq!(expected, got.unwrap().1); let expected = TerminalValues::Range(0, 255); let got = dec_val::>("d0-255"); assert_eq!(expected, got.unwrap().1) } #[test] fn test_hex_val() { let expected = TerminalValues::Concatenation(vec![0xCA, 0xFF, 0xEE]); let got = hex_val::>("xCA.FF.EE"); assert_eq!(expected, got.unwrap().1); let expected = TerminalValues::Range(0, 255); let got = hex_val::>("x00-FF"); assert_eq!(expected, got.unwrap().1) } #[test] fn test_prose_val() { assert_eq!( "Hello, World!", prose_val::>("") .unwrap() .1 ) } #[test] fn test_definition() { let tests = vec![ ("a =/ A\n", Rule::incremental("a", Node::rulename("A"))), ( "B =/ A / B\n", Rule::incremental( "B", Node::alternatives(&[Node::rulename("A"), Node::rulename("B")]), ), ), ]; for (test, expected) in tests { let (remaining, got) = rule_internal::>(test).unwrap(); assert!(remaining.is_empty()); assert_eq!(got, expected); } } #[quickcheck] fn test_explore_nesting(test: Rule) { // FIXME: This test can not fail currently. // Serialize an arbitrary rule to a string and check if it is parsed correctly. // This is useful to see how much of the API can be exposed without getting weird errors. // // Should... // rule == deserialize(serialize(rule)) // also be true? // // Findings: // * Repetition(Repetition(...)) is not parsable. let printed = test.to_string() + "\n"; if let Err(_) = rule_internal::>(&printed) { println!("# Found interesting rule:"); println!("{:#?}", test); println!("{}", test); } } #[test] fn test_repetition_repetition() { // FIXME: This test can not fail currently. let rule = Rule::new( "rule", Node::repetition( Repeat::variable(Some(1), Some(12)), Node::repetition(Repeat::variable(Some(1), Some(2)), Node::prose("test")), ), ); println!("{}", rule); } #[test] fn test_comment_unicode() { let (remaining, _) = comment::>(";a\n").unwrap(); assert_eq!(remaining, ""); let (remaining, _) = comment::>("; a\n").unwrap(); assert_eq!(remaining, ""); let (remaining, _) = comment::>("; a \n").unwrap(); assert_eq!(remaining, ""); let (remaining, _) = comment::>("; a \r\n").unwrap(); assert_eq!(remaining, ""); let (remaining, _) = comment::>(";²\n").unwrap(); assert_eq!(remaining, ""); let (remaining, _) = comment::>("; ²\n").unwrap(); assert_eq!(remaining, ""); let (remaining, _) = comment::>("; ² \n").unwrap(); assert_eq!(remaining, ""); let (remaining, _) = comment::>("; ² \r\n").unwrap(); assert_eq!(remaining, ""); let (remaining, _) = comment::>(";a\nx").unwrap(); assert_eq!(remaining, "x"); let (remaining, _) = comment::>("; a\nx").unwrap(); assert_eq!(remaining, "x"); let (remaining, _) = comment::>("; a \nx").unwrap(); assert_eq!(remaining, "x"); let (remaining, _) = comment::>("; a \r\nx").unwrap(); assert_eq!(remaining, "x"); let (remaining, _) = comment::>(";²\nx").unwrap(); assert_eq!(remaining, "x"); let (remaining, _) = comment::>("; ²\nx").unwrap(); assert_eq!(remaining, "x"); let (remaining, _) = comment::>("; ² \nx").unwrap(); assert_eq!(remaining, "x"); let (remaining, _) = comment::>("; ² \r\nx").unwrap(); assert_eq!(remaining, "x"); } #[test] fn test_file_abnf_core() { let imap = std::fs::read_to_string("examples/assets/abnf_core.abnf").unwrap(); rulelist(&imap).unwrap(); } #[test] fn test_file_abnf() { let imap = std::fs::read_to_string("examples/assets/abnf.abnf").unwrap(); rulelist(&imap).unwrap(); } #[test] fn test_file_imap() { let imap = std::fs::read_to_string("examples/assets/imap.abnf").unwrap(); rulelist(&imap).unwrap(); } #[test] fn test_relaxed_rule_parsing() { let expected = Rule::new("rule", Node::rulename("A")); let tests = [ "rule = A", "rule = A\n", " rule = A", " rule = A\n", "; Comment\nrule = A", "; Comment\nrule = A\n", "\n; Comment\nrule = A", "\n; Comment\nrule = A\n", "\n; Comment\n rule = A", "\n; Comment\n rule = A\n", "\n\n \n \n \n; Comment \n\n\n \n \n rule = \n A", ]; for test in &tests { println!("[#] {}", test); let got = rule(test).unwrap(); println!("[#] {:?}", got); assert_eq!(expected, got); println!("-------------------------------------------"); } } } abnf-0.13.0/src/types.rs000064400000000000000000000316551046102023000131260ustar 00000000000000//! This module contains a collection of all types in the ABNF crate. //! The types can also be used to manually construct new rules. //! //! # Example //! //! ``` //! use abnf::types::*; //! //! let rule = Rule::new("test", Node::alternatives(&[ //! Node::rulename("A"), //! Node::concatenation(&[ //! Node::rulename("B"), //! Node::rulename("C") //! ]) //! ])); //! //! println!("{}", rule); // prints "test = A / B C" //! ``` use std::fmt; /// Is a rule a basic rule or an incremental alternative? /// See #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Kind { /// Basic Rule Definition Basic, /// Incremental Alternative Incremental, } /// A single ABNF rule with a name, it's definition (implemented as `Node`) and a kind (`Kind::Basic` or `Kind::Incremental`). #[derive(Debug, Clone, Eq, PartialEq)] pub struct Rule { name: String, node: Node, kind: Kind, } impl Rule { /// Construct a basic rule. pub fn new(name: &str, node: Node) -> Rule { Rule { name: name.into(), node, kind: Kind::Basic, } } /// Construct an incremental rule. pub fn incremental(name: &str, node: Node) -> Rule { Rule { name: name.into(), node, kind: Kind::Incremental, } } /// Get the name of the rule. pub fn name(&self) -> &str { &self.name } /// Get the definition of the rule. Implemented as a composition of `Node`s. pub fn node(&self) -> &Node { &self.node } /// Get the kind of the rule, i.e. `Basic` or `Incremental`. pub fn kind(&self) -> Kind { self.kind } } /// A `Node` enumerates all building blocks in ABNF. /// Any rule is a composition of `Node`s. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Node { /// An alternation, e.g. `A / B / C`. Alternatives(Vec), /// A concatenation, e.g. `A B C`. Concatenation(Vec), /// A repetition, e.g. `*A`. Repetition { /// How often ... repeat: Repeat, /// ... is which node repeated? node: Box, }, /// A rulename, i.e. a non-terminal. Rulename(String), /// A group, e.g. `(A B)`. Group(Box), /// An option, e.g. `[A]`. Optional(Box), /// A literal text string/terminal, e.g. `"http"`, `%i"hTtP"` or `%s"http"`. String(StringLiteral), /// A single value within a range (e.g. `%x01-ff`) /// or a terminal defined by a series of values (e.g. `%x0f.f1.ce`). TerminalValues(TerminalValues), /// A prose string, i.e. ``. Prose(String), } impl Node { /// Constructor/Shorthand for Node::Alternatives(...). pub fn alternatives(nodes: &[Node]) -> Node { Node::Alternatives(nodes.to_vec()) } /// Constructor/Shorthand for Node::Concatenation(...). pub fn concatenation(nodes: &[Node]) -> Node { Node::Concatenation(nodes.to_vec()) } /// Constructor/Shorthand for Node::Repetition(...). pub fn repetition(repeat: Repeat, node: Node) -> Node { Node::Repetition { repeat, node: Box::new(node), } } /// Constructor/Shorthand for Node::Rulename(...). pub fn rulename>(name: S) -> Node { Node::Rulename(name.as_ref().to_string()) } /// Constructor/Shorthand for Node::Group(...). pub fn group(node: Node) -> Node { Node::Group(Box::new(node)) } /// Constructor/Shorthand for Node::Optional(...). pub fn optional(node: Node) -> Node { Node::Optional(Box::new(node)) } /// Constructor/Shorthand for Node::String(StringLiteral::new(...)). pub fn string>(string: S, case_sensitive: bool) -> Node { Node::String(StringLiteral::new( string.as_ref().to_owned(), case_sensitive, )) } /// Constructor/Shorthand for Node::TerminalValues(...). pub fn terminal_values(terminal_values: TerminalValues) -> Node { Node::TerminalValues(terminal_values) } /// Constructor/Shorthand for Node::Prose(...). pub fn prose>(prose: S) -> Node { Node::Prose(prose.as_ref().to_string()) } } /// String literal value, either case sensitive or not. #[derive(Debug, Clone, Eq, PartialEq)] pub struct StringLiteral { /// String value. value: String, /// Whether the string is case sensitive or not. case_sensitive: bool, } impl StringLiteral { /// Creates a new string literal. pub fn new(string: String, case_sensitive: bool) -> Self { Self { value: string, case_sensitive, } } /// Creates a new case insensitive string literal. pub fn case_insensitive(string: String) -> Self { Self { value: string, case_sensitive: false, } } /// Creates a new case sensitive string literal. pub fn case_sensitive(string: String) -> Self { Self { value: string, case_sensitive: true, } } /// Returns whether or not the string literal is case sensitive. pub fn is_case_sensitive(&self) -> bool { self.case_sensitive } /// Sets whether or not the string literal is case sensitive. pub fn set_case_sensitive(&mut self, case_sensitive: bool) { self.case_sensitive = case_sensitive } /// Returns the string content. pub fn value(&self) -> &str { &self.value } /// Returns the string content. /// /// Alias for [`value`](Self::value). pub fn as_str(&self) -> &str { &self.value } /// Returns a mutable reference to the string content. pub fn value_mut(&mut self) -> &mut String { &mut self.value } /// Sets the string literal's content. pub fn set_value(&mut self, value: String) { self.value = value } /// Turns this string literal into its content. pub fn into_value(self) -> String { self.value } /// Turns this string literal into a pair containing its content and a /// boolean stating if the string literal is case sensitive or not. pub fn into_parts(self) -> (String, bool) { (self.value, self.case_sensitive) } } impl From for StringLiteral { /// Converts a `String` into a case insensitive string literal. fn from(s: String) -> Self { Self::case_insensitive(s) } } impl fmt::Display for StringLiteral { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.case_sensitive { write!(f, "%s\"{}\"", self.value) } else { write!(f, "\"{}\"", self.value) } } } /// Defines the kind of repetition. This enum is defined in a way, /// which makes it possible to preserve the parsed variant. /// As a consequence, multiple logically equivalent forms can be represented, /// e.g. `4 == 4*4`, `*5 == 0*5`, etc. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Repeat { /// Repeat exactly n times, e.g. `4`. Specific(usize), /// Repeat with optionally lower and optionally upper bounded value, e.g. `1*3`. /// Both bounds are inclusive. Variable { /// Lower bound (inclusive). min: Option, /// Upper bound (inclusive). max: Option, }, } impl Repeat { /// Create an unbounded repeat value, i.e. `*`. pub fn unbounded() -> Self { Self::Variable { min: None, max: None, } } /// Create a specific repeat value by providing n. pub fn specific(n: usize) -> Self { Self::Specific(n) } /// Create a variable repeat value by providing both lower and upper bound. pub fn variable(min: Option, max: Option) -> Self { Self::Variable { min, max } } /// Get the lower bound. pub fn min(&self) -> Option { match *self { Self::Specific(n) => Some(n), Self::Variable { min, .. } => min, } } /// Get the upper bound. pub fn max(&self) -> Option { match *self { Self::Specific(n) => Some(n), Self::Variable { max, .. } => max, } } /// Get the lower and upper bound as a tuple. pub fn min_max(&self) -> (Option, Option) { (self.min(), self.max()) } } /// Terminal created by numerical values. #[derive(Debug, Clone, Eq, PartialEq)] pub enum TerminalValues { // FIXME: Keep base (bin, dec, hex) // Relaxed for Unicode support, see . /// A single value within a range (e.g. `%x01-ff`). Range(u32, u32), /// A terminal defined by a concatenation of values (e.g. `%x0f.f1.ce`). Concatenation(Vec), } impl TerminalValues { /// Create an alternation from a lower and upper bound (both inclusive). /// See ABNF's "value range alternatives", e.g. `%c##-##`. pub fn range(from: u32, to: u32) -> TerminalValues { TerminalValues::Range(from, to) } /// Create a terminal from a series of values. /// See ABNF's terminal notation, e.g. `%c##.##.##`. pub fn sequence(alts: &[u32]) -> TerminalValues { TerminalValues::Concatenation(alts.to_owned()) } } impl fmt::Display for Rule { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name)?; match self.kind { Kind::Basic => write!(f, " = ")?, Kind::Incremental => write!(f, " =/ ")?, } write!(f, "{}", self.node) } } impl fmt::Display for Node { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Node::Alternatives(nodes) => { if let Some((last, elements)) = nodes.split_last() { for item in elements { write!(f, "{} / ", item)?; } write!(f, "{}", last)?; } } Node::Concatenation(nodes) => { if let Some((last, elements)) = nodes.split_last() { for item in elements { write!(f, "{} ", item)?; } write!(f, "{}", last)?; } } Node::Repetition { repeat, node } => { match *repeat { Repeat::Specific(n) => { write!(f, "{}", n)?; } Repeat::Variable { min, max } => { if let Some(min) = min { write!(f, "{}", min)?; } write!(f, "*")?; if let Some(max) = max { write!(f, "{}", max)?; } } } write!(f, "{}", node)?; } Node::Rulename(name) => { write!(f, "{}", name)?; } Node::Group(node) => { write!(f, "({})", node)?; } Node::Optional(node) => { write!(f, "[{}]", node)?; } Node::String(str) => { str.fmt(f)?; } Node::TerminalValues(range) => { write!(f, "%x")?; match range { TerminalValues::Concatenation(allowed) => { if let Some((last, elements)) = allowed.split_last() { for item in elements { write!(f, "{:02X}.", item)?; } write!(f, "{:02X}", last)?; } } TerminalValues::Range(from, to) => { write!(f, "{:02X}-{:02X}", from, to)?; } } } Node::Prose(str) => { write!(f, "<{}>", str)?; } } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_display_rule() { let test = Rule::new("rule", Node::rulename("A")); let expected = "rule = A"; let got = test.to_string(); assert_eq!(expected, got); let test = Rule::incremental("rule", Node::rulename("A")); let expected = "rule =/ A"; let got = test.to_string(); assert_eq!(expected, got); } #[test] fn test_display_prose() { let rule = Rule::new("rule", Node::prose("test")); assert_eq!("rule = ", rule.to_string()); } #[test] fn test_impl_trait() { // Make sure that others can implement their own traits for Rule. trait Foo { fn foo(&self); } impl Foo for Rule { fn foo(&self) { println!("{}", self.name); } } } }