├── .github ├── renovate.json5 ├── settings.yml └── workflows │ ├── audit.yml │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bench.py ├── deny.toml ├── examples ├── chumsky-app │ ├── Cargo.toml │ ├── app.rs │ └── parser.rs ├── combine-app │ ├── Cargo.toml │ ├── app.rs │ └── parser.rs ├── grmtools-app │ ├── Cargo.toml │ ├── app.rs │ ├── build.rs │ ├── json.l │ ├── json.y │ └── json_val.rs ├── lalrpop-app │ ├── Cargo.toml │ ├── app.rs │ ├── build.rs │ ├── foo.rs │ ├── json.lalrpop │ └── json_val.rs ├── lelwel-app │ ├── Cargo.toml │ ├── app.rs │ ├── generated.rs │ ├── json.llw │ ├── lexer.rs │ ├── parser.rs │ └── tests │ │ └── codegen.rs ├── logos-app │ ├── Cargo.toml │ ├── app.rs │ └── parser.rs ├── nom-app │ ├── Cargo.toml │ ├── app.rs │ └── parser.rs ├── null-app │ ├── Cargo.toml │ └── app.rs ├── parol-app │ ├── Cargo.toml │ ├── app.rs │ ├── grammar.rs │ ├── grammar_trait.rs │ ├── json.par │ ├── parser.rs │ └── tests │ │ └── codegen.rs ├── peg-app │ ├── Cargo.toml │ ├── app.rs │ └── parser.rs ├── pest-app │ ├── Cargo.toml │ ├── app.rs │ ├── json.pest │ └── parser.rs ├── serde_json-app │ ├── Cargo.toml │ └── app.rs ├── winnow-app │ ├── Cargo.toml │ ├── app.rs │ ├── json.rs │ └── parser.rs └── yap-app │ ├── Cargo.toml │ ├── app.rs │ └── parser.rs ├── format.py ├── runs ├── 2022-07-14-Doomslug.json ├── 2022-07-18-Doomslug.json ├── 2023-02-23-seon.json ├── 2023-03-07-seon.json ├── 2023-03-23-seon.json ├── 2023-07-13-seon.json ├── 2023-09-18-seon.json ├── 2024-01-26-seon.json ├── 2024-02-07-seon.json ├── 2024-06-04-seon.json ├── 2024-07-18-seon.json ├── 2025-01-27-seon.json ├── 2025-01-30-seon.json ├── 2025-03-28-seon.json ├── 2025-04-01-seon.json ├── 2025-04-14-seon.json └── 2025-05-01-seon.json └── third_party └── nativejson-benchmark ├── LINK └── data └── canada.json /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | schedule: [ 3 | 'before 5am on the first day of the month', 4 | ], 5 | semanticCommits: 'enabled', 6 | configMigration: true, 7 | dependencyDashboard: true, 8 | packageRules: [ 9 | // Goals: 10 | // - Rollup safe upgrades to reduce CI runner load 11 | // - Have lockfile and manifest in-sync 12 | { 13 | matchManagers: [ 14 | 'cargo', 15 | ], 16 | matchCurrentVersion: '>=0.1.0', 17 | matchUpdateTypes: [ 18 | 'patch', 19 | ], 20 | automerge: true, 21 | groupName: 'compatible', 22 | }, 23 | { 24 | matchManagers: [ 25 | 'cargo', 26 | ], 27 | matchCurrentVersion: '>=1.0.0', 28 | matchUpdateTypes: [ 29 | 'minor', 30 | ], 31 | automerge: true, 32 | groupName: 'compatible', 33 | }, 34 | ], 35 | } 36 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # These settings are synced to GitHub by https://probot.github.io/apps/settings/ 2 | 3 | repository: 4 | description: Comparing parser APIs 5 | topics: rust parser 6 | has_issues: true 7 | has_projects: false 8 | has_wiki: false 9 | has_downloads: true 10 | default_branch: main 11 | 12 | allow_squash_merge: true 13 | allow_merge_commit: true 14 | allow_rebase_merge: true 15 | 16 | # Manual: allow_auto_merge: true, see https://github.com/probot/settings/issues/402 17 | delete_branch_on_merge: true 18 | 19 | labels: 20 | # Type 21 | - name: bug 22 | color: '#b60205' 23 | description: Not as expected 24 | - name: enhancement 25 | color: '#1d76db' 26 | description: Improve the expected 27 | # Flavor 28 | - name: question 29 | color: "#cc317c" 30 | description: Uncertainty is involved 31 | - name: breaking-change 32 | color: "#e99695" 33 | - name: good first issue 34 | color: '#c2e0c6' 35 | description: Help wanted! 36 | 37 | branches: 38 | - name: main 39 | protection: 40 | required_pull_request_reviews: null 41 | required_conversation_resolution: true 42 | required_status_checks: 43 | # Required. Require branches to be up to date before merging. 44 | strict: false 45 | contexts: ["CI"] 46 | enforce_admins: false 47 | restrictions: null 48 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '**/Cargo.toml' 10 | - '**/Cargo.lock' 11 | push: 12 | branches: 13 | - main 14 | 15 | env: 16 | RUST_BACKTRACE: 1 17 | CARGO_TERM_COLOR: always 18 | CLICOLOR: 1 19 | 20 | concurrency: 21 | group: "${{ github.workflow }}-${{ github.ref }}" 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | security_audit: 26 | permissions: 27 | issues: write # to create issues (actions-rs/audit-check) 28 | checks: write # to create check (actions-rs/audit-check) 29 | runs-on: ubuntu-latest 30 | # Prevent sudden announcement of a new advisory from failing ci: 31 | continue-on-error: true 32 | steps: 33 | - name: Checkout repository 34 | uses: actions/checkout@v4 35 | - uses: actions-rs/audit-check@v1 36 | with: 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | cargo_deny: 40 | permissions: 41 | issues: write # to create issues (actions-rs/audit-check) 42 | checks: write # to create check (actions-rs/audit-check) 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | checks: 47 | - bans licenses sources 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: EmbarkStudios/cargo-deny-action@v2 51 | with: 52 | command: check ${{ matrix.checks }} 53 | rust-version: stable 54 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | 12 | env: 13 | RUST_BACKTRACE: 1 14 | CARGO_TERM_COLOR: always 15 | CLICOLOR: 1 16 | 17 | concurrency: 18 | group: "${{ github.workflow }}-${{ github.ref }}" 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | smoke: 23 | name: Quick Check 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | - name: Install Rust 29 | uses: dtolnay/rust-toolchain@stable 30 | with: 31 | toolchain: stable 32 | - uses: Swatinem/rust-cache@v2 33 | - name: Default features 34 | run: cargo check --workspace --all-targets 35 | test: 36 | name: Test 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v4 41 | - name: Install Rust 42 | uses: dtolnay/rust-toolchain@stable 43 | with: 44 | toolchain: stable 45 | - uses: Swatinem/rust-cache@v2 46 | - name: Default features 47 | run: cargo test --workspace 48 | rustfmt: 49 | name: rustfmt 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Checkout repository 53 | uses: actions/checkout@v4 54 | - name: Install Rust 55 | uses: dtolnay/rust-toolchain@stable 56 | with: 57 | toolchain: stable 58 | components: rustfmt 59 | - uses: Swatinem/rust-cache@v2 60 | - name: Check formatting 61 | run: cargo fmt --all -- --check 62 | clippy: 63 | name: clippy 64 | runs-on: ubuntu-latest 65 | permissions: 66 | security-events: write # to upload sarif results 67 | steps: 68 | - name: Checkout repository 69 | uses: actions/checkout@v4 70 | - name: Install Rust 71 | uses: dtolnay/rust-toolchain@stable 72 | with: 73 | toolchain: stable 74 | components: clippy 75 | - uses: Swatinem/rust-cache@v2 76 | - name: Install SARIF tools 77 | run: cargo install clippy-sarif --locked 78 | - name: Install SARIF tools 79 | run: cargo install sarif-fmt --locked 80 | - name: Check 81 | run: > 82 | cargo clippy --workspace --all-features --all-targets --message-format=json 83 | | clippy-sarif 84 | | tee clippy-results.sarif 85 | | sarif-fmt 86 | continue-on-error: true 87 | - name: Upload 88 | uses: github/codeql-action/upload-sarif@v3 89 | with: 90 | sarif_file: clippy-results.sarif 91 | wait-for-processing: true 92 | - name: Report status 93 | run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | /.idea 4 | /*.iml 5 | examples/lalrpop-app/json.rs 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "examples/*", 5 | ] 6 | 7 | [workspace.package] 8 | edition = "2021" 9 | 10 | [workspace.lints.rust] 11 | dead_code = "allow" # relying on `black_box` / debug repr 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Evgeniy Reizner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Parsing Benchmarks 2 | 3 | This repo tries to assess Rust parsing performance. 4 | 5 | | crate | parser type | action code | integration | input type | precedence | parameterized rules | streaming input | 6 | |------------|---------------|-------------|--------------------|-------------------------|------------------------|---------------------|-----------------| 7 | | [chumsky] | combinators | in source | library | `&str`, `&[u8]`, custom | [pratt][chumsky-pratt] | Yes | Yes | 8 | | [combine] | combinators | in source | library | `&str` | ? | ? | ? | 9 | | [grmtools] | CFG | in grammar | library | ? | ? | ? | ? | 10 | | [lalrpop] | LR(1) | in grammar | build script | `&str` | none | Yes | No | 11 | | [lelwel] | LL(1) | in grammar | build script | `&str` | pratt | Yes | No | 12 | | [logos] | lexer | in source | proc macro | `&str`, `&[u8]` | ? | ? | ? | 13 | | [nom] | combinators | in source | library | `&str`, `&[u8]`, custom | [pratt][nom-pratt] | Yes | Yes | 14 | | [parol] | LL(k)/LALR(R) | in grammar | build script | `&str` | none | ? | No | 15 | | [peg] | PEG | in grammar | proc macro (block) | `&str`, `&[T]`, custom | climbing | Yes | No | 16 | | [pest] | PEG | external | proc macro (file) | `&str` | climbing | No | No | 17 | | [winnow] | combinators | in source | library | `&str`, `&[T]`, custom | none | Yes | Yes | 18 | | [yap] | combinators | in source | library | `&str`, `&[T]`, custom | none | Yes | ? | 19 | 20 | Formerly, we compared: 21 | - [pom]: lack of notoriety 22 | 23 | # Results 24 | 25 | Name | Overhead (release) | Build (debug) | Parse (release) | Downloads | Version 26 | -----|--------------------|---------------|-----------------|-----------|-------- 27 | null | 0 KiB | 217ms | 4ms | - | - 28 | grmtools | 2,623 KiB | 12s | 174ms | ![Download count](https://img.shields.io/crates/dr/cfgrammar) | v0.13.10 29 | chumsky | 117 KiB | 4s | 31ms | ![Download count](https://img.shields.io/crates/dr/chumsky) | v0.10.1 30 | combine | 184 KiB | 4s | 50ms | ![Download count](https://img.shields.io/crates/dr/combine) | v3.8.1 31 | lalrpop | 1,527 KiB | 12s | 38ms | ![Download count](https://img.shields.io/crates/dr/lalrpop) | v0.22.1 32 | lelwel | 148 KiB | 5s | 10ms | ![Download count](https://img.shields.io/crates/dr/lelwel) | v0.8.0 33 | logos | 90 KiB | 5s | 21ms | ![Download count](https://img.shields.io/crates/dr/logos) | v0.15.0 34 | nom | 94 KiB | 3s | 65ms | ![Download count](https://img.shields.io/crates/dr/nom) | v8.0.0 35 | parol | 1,718 KiB | 14s | 239ms | ![Download count](https://img.shields.io/crates/dr/parol) | v3.0.1 36 | peg | 84 KiB | 2s | 22ms | ![Download count](https://img.shields.io/crates/dr/peg) | v0.8.5 37 | pest | 130 KiB | 6s | 62ms | ![Download count](https://img.shields.io/crates/dr/pest) | v2.8.0 38 | serde_json | 59 KiB | 3s | 15ms | ![Download count](https://img.shields.io/crates/dr/serde_json) | v1.0.140 39 | winnow | 75 KiB | 2s | 29ms | ![Download count](https://img.shields.io/crates/dr/winnow) | v0.7.8 40 | yap | 65 KiB | 499ms | 33ms | ![Download count](https://img.shields.io/crates/dr/yap) | v0.12.0 41 | 42 | *System: Linux 6.8.0-58-generic (x86_64), rustc 1.86.0 (05f9846f8 2025-03-31) w/ `-j 8`* 43 | 44 | Note: 45 | - For more "Parse (release)" comparisons, see [parser_benchmarks](https://github.com/rust-bakery/parser_benchmarks) 46 | - Parsers have not been validated and might have differing levels of quality ([#5](https://github.com/epage/parse-benchmarks-rs/issues/5)) 47 | 48 | # Running the Benchmarks 49 | 50 | ```bash 51 | $ ./bench.py 52 | $ ./format.py 53 | ``` 54 | 55 | [chumsky]: https://github.com/zesterer/chumsky 56 | [chumsky-pratt]: https://docs.rs/chumsky/latest/chumsky/pratt/index.html 57 | [combine]: https://github.com/Marwes/combine 58 | [lalrpop]: https://github.com/lalrpop/lalrpop 59 | [lelwel]: https://github.com/0x2a-42/lelwel 60 | [logos]: https://github.com/maciejhirsz/logos 61 | [nom]: https://github.com/geal/nom 62 | [nom-pratt]: https://docs.rs/nom-language/latest/nom_language/precedence/fn.precedence.html 63 | [parol]: https://github.com/jsinger67/parol 64 | [peg]: https://github.com/kevinmehall/rust-peg 65 | [pest]: https://github.com/pest-parser/pest 66 | [pom]: https://github.com/j-f-liu/pom 67 | [winnow]: https://github.com/winnow-rs/winnow 68 | [yap]: https://github.com/jsdw/yap 69 | [yap]: https://github.com/jsdw/yap 70 | [grmtools]: https://crates.io/crates/cfgrammar 71 | -------------------------------------------------------------------------------- /bench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import copy 4 | import datetime 5 | import json 6 | import multiprocessing 7 | import pathlib 8 | import platform 9 | import subprocess 10 | import sys 11 | import tempfile 12 | 13 | 14 | def main(): 15 | repo_root = pathlib.Path(__name__).parent 16 | 17 | timestamp = datetime.datetime.now().strftime("%Y-%m-%d") 18 | hostname = platform.node() 19 | uname = platform.uname() 20 | cpus = multiprocessing.cpu_count() 21 | rustc = subprocess.run(["rustc", "--version"], check=True, capture_output=True, encoding="utf-8").stdout.strip() 22 | 23 | extension = ".exe" if sys.platform in ("win32", "cygwin") else "" 24 | 25 | runs_root = repo_root / "runs" 26 | runs_root.mkdir(parents=True, exist_ok=True) 27 | raw_run_path = runs_root / "{}-{}.json".format(timestamp, hostname) 28 | if raw_run_path.exists(): 29 | old_raw_run = json.loads(raw_run_path.read_text()) 30 | else: 31 | old_raw_run = {} 32 | 33 | raw_run = { 34 | "timestamp": timestamp, 35 | "hostname": hostname, 36 | "os": uname.system, 37 | "os_ver": uname.release, 38 | "arch": uname.machine, 39 | "cpus": cpus, 40 | "rustc": rustc, 41 | "libs": {}, 42 | } 43 | 44 | with tempfile.TemporaryDirectory() as tmpdir: 45 | for example_path in sorted((repo_root / "examples").glob("*-app")): 46 | manifest_path = example_path / "Cargo.toml" 47 | name = example_path.name.rsplit("-", 1)[0] 48 | metadata = harvest_metadata(manifest_path, name) 49 | 50 | build_report_path = pathlib.Path(tmpdir) / f"{example_path.name}-build.json" 51 | if True: 52 | hyperfine_cmd = [ 53 | "hyperfine", 54 | "--warmup=1", 55 | "--min-runs=5", 56 | f"--export-json={build_report_path}", 57 | "--prepare=cargo clean", 58 | # Doing debug builds because that is more likely the 59 | # time directly impacting people 60 | f"cargo build -j {cpus} --package {example_path.name}" 61 | ] 62 | if False: 63 | hyperfine_cmd.append("--show-output") 64 | subprocess.run( 65 | hyperfine_cmd, 66 | cwd=repo_root, 67 | check=True, 68 | ) 69 | build_report = json.loads(build_report_path.read_text()) 70 | else: 71 | build_report = old_raw_run.get("libs", {}).get(str(manifest_path), {}).get("build", None) 72 | 73 | if True: 74 | # Doing release builds because that is where size probably matters most 75 | subprocess.run(["cargo", "build", "--release", "--package", example_path.name], cwd=repo_root, check=True) 76 | app_path = repo_root / f"target/release/{example_path.name}{extension}" 77 | file_size = app_path.stat().st_size 78 | else: 79 | app_path = None 80 | file_size = old_raw_run.get("libs", {}).get(str(manifest_path), {}).get("size", None) 81 | 82 | run_report_path = pathlib.Path(tmpdir) / f"{example_path.name}-run.json" 83 | if True and app_path is not None: 84 | json_path = pathlib.Path(__file__).parent / "third_party/nativejson-benchmark/data/canada.json" 85 | assert json_path.exists() 86 | hyperfine_cmd = [ 87 | "hyperfine", 88 | "--warmup=1", 89 | "--min-runs=5", 90 | f"--export-json={run_report_path}", 91 | f"{app_path} {json_path}" 92 | ] 93 | if False: 94 | hyperfine_cmd.append("--show-output") 95 | subprocess.run( 96 | hyperfine_cmd, 97 | cwd=repo_root, 98 | check=True, 99 | ) 100 | run_report = json.loads(run_report_path.read_text()) 101 | else: 102 | run_report = old_raw_run.get("libs", {}).get(str(manifest_path), {}).get("run", None) 103 | 104 | raw_run["libs"][str(manifest_path)] = { 105 | "name": example_path.name.rsplit("-", 1)[0], 106 | "manifest_path": str(manifest_path), 107 | "crate": metadata["name"], 108 | "version": metadata["version"], 109 | "build": build_report, 110 | "run": run_report, 111 | "size": file_size, 112 | } 113 | 114 | raw_run_path.write_text(json.dumps(raw_run, indent=2)) 115 | print(raw_run_path) 116 | 117 | 118 | def harvest_metadata(manifest_path, name): 119 | p = subprocess.run(["cargo", "tree"], check=True, cwd=manifest_path.parent, capture_output=True, encoding="utf-8") 120 | lines = p.stdout.strip().splitlines() 121 | app_line = lines.pop(0) 122 | if lines: 123 | self_line = lines[0] 124 | first_name, _ = _extract_line(self_line) 125 | unique = dict( 126 | _extract_line(line) 127 | for line in lines 128 | if "(*)" not in line and "[build-dependencies]" not in line and "[dev-dependencies" not in line 129 | ) 130 | version = unique.get(name) 131 | if version is None: 132 | name = first_name 133 | version = unique.get(first_name) 134 | else: 135 | name = None 136 | version = None 137 | 138 | return { 139 | "name": name, 140 | "version": version, 141 | } 142 | 143 | 144 | def _extract_line(line): 145 | if line.endswith(" (proc-macro)"): 146 | line = line[0:-len(" (proc-macro)")] 147 | _, name, version = line.rsplit(" ", 2) 148 | return name, version 149 | 150 | 151 | 152 | if __name__ == "__main__": 153 | main() 154 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Note that all fields that take a lint level have these possible values: 2 | # * deny - An error will be produced and the check will fail 3 | # * warn - A warning will be produced, but the check will not fail 4 | # * allow - No warning or error will be produced, though in some cases a note 5 | # will be 6 | 7 | # Root options 8 | 9 | # The graph table configures how the dependency graph is constructed and thus 10 | # which crates the checks are performed against 11 | [graph] 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #"x86_64-unknown-linux-musl", 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | # When creating the dependency graph used as the source of truth when checks are 30 | # executed, this field can be used to prune crates from the graph, removing them 31 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 32 | # is pruned from the graph, all of its dependencies will also be pruned unless 33 | # they are connected to another crate in the graph that hasn't been pruned, 34 | # so it should be used with care. The identifiers are [Package ID Specifications] 35 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 36 | #exclude = [] 37 | # If true, metadata will be collected with `--all-features`. Note that this can't 38 | # be toggled off if true, if you want to conditionally enable `--all-features` it 39 | # is recommended to pass `--all-features` on the cmd line instead 40 | all-features = false 41 | # If true, metadata will be collected with `--no-default-features`. The same 42 | # caveat with `all-features` applies 43 | no-default-features = false 44 | # If set, these feature will be enabled when collecting metadata. If `--features` 45 | # is specified on the cmd line they will take precedence over this option. 46 | #features = [] 47 | 48 | # The output table provides options for how/if diagnostics are outputted 49 | [output] 50 | # When outputting inclusion graphs in diagnostics that include features, this 51 | # option can be used to specify the depth at which feature edges will be added. 52 | # This option is included since the graphs can be quite large and the addition 53 | # of features from the crate(s) to all of the graph roots can be far too verbose. 54 | # This option can be overridden via `--feature-depth` on the cmd line 55 | feature-depth = 1 56 | 57 | # This section is considered when running `cargo deny check advisories` 58 | # More documentation for the advisories section can be found here: 59 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 60 | [advisories] 61 | # The path where the advisory databases are cloned/fetched into 62 | #db-path = "$CARGO_HOME/advisory-dbs" 63 | # The url(s) of the advisory databases to use 64 | #db-urls = ["https://github.com/rustsec/advisory-db"] 65 | # A list of advisory IDs to ignore. Note that ignored advisories will still 66 | # output a note when they are encountered. 67 | ignore = [ 68 | #"RUSTSEC-0000-0000", 69 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, 70 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish 71 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, 72 | ] 73 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 74 | # If this is false, then it uses a built-in git library. 75 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 76 | # See Git Authentication for more information about setting up git authentication. 77 | #git-fetch-with-cli = true 78 | 79 | # This section is considered when running `cargo deny check licenses` 80 | # More documentation for the licenses section can be found here: 81 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 82 | [licenses] 83 | # List of explicitly allowed licenses 84 | # See https://spdx.org/licenses/ for list of possible licenses 85 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 86 | allow = [ 87 | "MIT", 88 | "MIT-0", 89 | "Apache-2.0", 90 | "BSD-2-Clause", 91 | "BSD-3-Clause", 92 | "MPL-2.0", 93 | "Unicode-DFS-2016", 94 | "CC0-1.0", 95 | "ISC", 96 | "OpenSSL", 97 | ] 98 | # The confidence threshold for detecting a license from license text. 99 | # The higher the value, the more closely the license text must be to the 100 | # canonical license text of a valid SPDX license file. 101 | # [possible values: any between 0.0 and 1.0]. 102 | confidence-threshold = 0.8 103 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 104 | # aren't accepted for every possible crate as with the normal allow list 105 | exceptions = [ 106 | # Each entry is the crate and version constraint, and its specific allow 107 | # list 108 | #{ allow = ["Zlib"], crate = "adler32" }, 109 | ] 110 | 111 | # Some crates don't have (easily) machine readable licensing information, 112 | # adding a clarification entry for it allows you to manually specify the 113 | # licensing information 114 | [[licenses.clarify]] 115 | # The package spec the clarification applies to 116 | crate = "ring" 117 | # The SPDX expression for the license requirements of the crate 118 | expression = "MIT AND ISC AND OpenSSL" 119 | # One or more files in the crate's source used as the "source of truth" for 120 | # the license expression. If the contents match, the clarification will be used 121 | # when running the license check, otherwise the clarification will be ignored 122 | # and the crate will be checked normally, which may produce warnings or errors 123 | # depending on the rest of your configuration 124 | license-files = [ 125 | # Each entry is a crate relative path, and the (opaque) hash of its contents 126 | { path = "LICENSE", hash = 0xbd0eed23 } 127 | ] 128 | 129 | [licenses.private] 130 | # If true, ignores workspace crates that aren't published, or are only 131 | # published to private registries. 132 | # To see how to mark a crate as unpublished (to the official registry), 133 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 134 | ignore = true 135 | # One or more private registries that you might publish crates to, if a crate 136 | # is only published to private registries, and ignore is true, the crate will 137 | # not have its license(s) checked 138 | registries = [ 139 | #"https://sekretz.com/registry 140 | ] 141 | 142 | # This section is considered when running `cargo deny check bans`. 143 | # More documentation about the 'bans' section can be found here: 144 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 145 | [bans] 146 | # Lint level for when multiple versions of the same crate are detected 147 | multiple-versions = "warn" 148 | # Lint level for when a crate version requirement is `*` 149 | wildcards = "allow" 150 | # The graph highlighting used when creating dotgraphs for crates 151 | # with multiple versions 152 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 153 | # * simplest-path - The path to the version with the fewest edges is highlighted 154 | # * all - Both lowest-version and simplest-path are used 155 | highlight = "all" 156 | # The default lint level for `default` features for crates that are members of 157 | # the workspace that is being checked. This can be overridden by allowing/denying 158 | # `default` on a crate-by-crate basis if desired. 159 | workspace-default-features = "allow" 160 | # The default lint level for `default` features for external crates that are not 161 | # members of the workspace. This can be overridden by allowing/denying `default` 162 | # on a crate-by-crate basis if desired. 163 | external-default-features = "allow" 164 | # List of crates that are allowed. Use with care! 165 | allow = [ 166 | #"ansi_term@0.11.0", 167 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, 168 | ] 169 | # List of crates to deny 170 | deny = [ 171 | #"ansi_term@0.11.0", 172 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, 173 | # Wrapper crates can optionally be specified to allow the crate when it 174 | # is a direct dependency of the otherwise banned crate 175 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, 176 | ] 177 | 178 | # List of features to allow/deny 179 | # Each entry the name of a crate and a version range. If version is 180 | # not specified, all versions will be matched. 181 | #[[bans.features]] 182 | #crate = "reqwest" 183 | # Features to not allow 184 | #deny = ["json"] 185 | # Features to allow 186 | #allow = [ 187 | # "rustls", 188 | # "__rustls", 189 | # "__tls", 190 | # "hyper-rustls", 191 | # "rustls", 192 | # "rustls-pemfile", 193 | # "rustls-tls-webpki-roots", 194 | # "tokio-rustls", 195 | # "webpki-roots", 196 | #] 197 | # If true, the allowed features must exactly match the enabled feature set. If 198 | # this is set there is no point setting `deny` 199 | #exact = true 200 | 201 | # Certain crates/versions that will be skipped when doing duplicate detection. 202 | skip = [ 203 | #"ansi_term@0.11.0", 204 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, 205 | ] 206 | # Similarly to `skip` allows you to skip certain crates during duplicate 207 | # detection. Unlike skip, it also includes the entire tree of transitive 208 | # dependencies starting at the specified crate, up to a certain depth, which is 209 | # by default infinite. 210 | skip-tree = [ 211 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies 212 | #{ crate = "ansi_term@0.11.0", depth = 20 }, 213 | ] 214 | 215 | # This section is considered when running `cargo deny check sources`. 216 | # More documentation about the 'sources' section can be found here: 217 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 218 | [sources] 219 | # Lint level for what to happen when a crate from a crate registry that is not 220 | # in the allow list is encountered 221 | unknown-registry = "deny" 222 | # Lint level for what to happen when a crate from a git repository that is not 223 | # in the allow list is encountered 224 | unknown-git = "deny" 225 | # List of URLs for allowed crate registries. Defaults to the crates.io index 226 | # if not specified. If it is specified but empty, no registries are allowed. 227 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 228 | # List of URLs for allowed Git repositories 229 | allow-git = [] 230 | 231 | [sources.allow-org] 232 | # 1 or more github.com organizations to allow git sources for 233 | github = [] 234 | # 1 or more gitlab.com organizations to allow git sources for 235 | gitlab = [] 236 | # 1 or more bitbucket.org organizations to allow git sources for 237 | bitbucket = [] 238 | -------------------------------------------------------------------------------- /examples/chumsky-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chumsky-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "chumsky-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | chumsky = "0.10.1" 11 | 12 | [lints] 13 | workspace = true 14 | -------------------------------------------------------------------------------- /examples/chumsky-app/app.rs: -------------------------------------------------------------------------------- 1 | //! This is a parser for JSON. 2 | //! Run it with the following command: 3 | //! cargo run --example json -- examples/sample.json 4 | 5 | mod parser; 6 | 7 | use std::{env, fs}; 8 | 9 | use chumsky::Parser; 10 | 11 | fn main() { 12 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 13 | .expect("Failed to read file"); 14 | 15 | let (json, errs) = parser::parser().parse(&src).into_output_errors(); 16 | #[cfg(debug_assertions)] 17 | { 18 | println!("{:#?}", json); 19 | } 20 | #[cfg(not(debug_assertions))] 21 | { 22 | std::hint::black_box(json); 23 | } 24 | for err in errs { 25 | eprintln!("{err}"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/chumsky-app/parser.rs: -------------------------------------------------------------------------------- 1 | //! This is a parser for JSON. 2 | //! Run it with the following command: 3 | //! cargo run --example json -- examples/sample.json 4 | 5 | use chumsky::prelude::*; 6 | use std::collections::HashMap; 7 | 8 | #[derive(Clone, Debug)] 9 | pub enum Json { 10 | Invalid, 11 | Null, 12 | Bool(bool), 13 | Str(String), 14 | Num(f64), 15 | Array(Vec), 16 | Object(HashMap), 17 | } 18 | 19 | pub fn parser<'a>() -> impl Parser<'a, &'a str, Json> { 20 | recursive(|value| { 21 | let digits = text::digits(10).to_slice(); 22 | 23 | let frac = just('.').then(digits); 24 | 25 | let exp = just('e') 26 | .or(just('E')) 27 | .then(one_of("+-").or_not()) 28 | .then(digits); 29 | 30 | let number = just('-') 31 | .or_not() 32 | .then(text::int(10)) 33 | .then(frac.or_not()) 34 | .then(exp.or_not()) 35 | .to_slice() 36 | .map(|s: &str| s.parse().unwrap()); 37 | 38 | let escape = just('\\') 39 | .then(choice(( 40 | just('\\'), 41 | just('/'), 42 | just('"'), 43 | just('b').to('\x08'), 44 | just('f').to('\x0C'), 45 | just('n').to('\n'), 46 | just('r').to('\r'), 47 | just('t').to('\t'), 48 | just('u').ignore_then(text::digits(16).exactly(4).to_slice().validate( 49 | |digits, _e, emitter| { 50 | char::from_u32(u32::from_str_radix(digits, 16).unwrap()).unwrap_or_else( 51 | || { 52 | emitter.emit(Default::default()); 53 | '\u{FFFD}' // unicode replacement character 54 | }, 55 | ) 56 | }, 57 | )), 58 | ))) 59 | .ignored(); 60 | 61 | let string = none_of("\\\"") 62 | .ignored() 63 | .or(escape) 64 | .repeated() 65 | .to_slice() 66 | .map(ToString::to_string) 67 | .delimited_by(just('"'), just('"')); 68 | 69 | let array = value 70 | .clone() 71 | .separated_by(just(',').padded()) 72 | .allow_trailing() 73 | .collect() 74 | .padded() 75 | .delimited_by(just('['), just(']')); 76 | 77 | let member = string.clone().then_ignore(just(':').padded()).then(value); 78 | let object = member 79 | .clone() 80 | .separated_by(just(',').padded()) 81 | .collect() 82 | .padded() 83 | .delimited_by(just('{'), just('}')); 84 | 85 | choice(( 86 | just("null").to(Json::Null), 87 | just("true").to(Json::Bool(true)), 88 | just("false").to(Json::Bool(false)), 89 | number.map(Json::Num), 90 | string.map(Json::Str), 91 | array.map(Json::Array), 92 | object.map(Json::Object), 93 | )) 94 | .padded() 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /examples/combine-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "combine-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "combine-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | combine = "3.8.1" 11 | 12 | [lints] 13 | workspace = true 14 | -------------------------------------------------------------------------------- /examples/combine-app/app.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate combine; 3 | 4 | mod parser; 5 | 6 | use std::{env, fs}; 7 | 8 | use combine::Parser; 9 | 10 | fn main() { 11 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 12 | .expect("Failed to read file"); 13 | 14 | let mut parser = parser::json_value(); 15 | match parser.easy_parse(src.as_bytes()) { 16 | Ok(json) => { 17 | #[cfg(debug_assertions)] 18 | { 19 | println!("{:#?}", json); 20 | } 21 | #[cfg(not(debug_assertions))] 22 | { 23 | std::hint::black_box(json); 24 | } 25 | } 26 | Err(err) => { 27 | eprintln!("{:#?}", err); 28 | std::process::exit(1); 29 | } 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /examples/combine-app/parser.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use combine::error::ParseError; 4 | use combine::{Parser, RangeStream, StreamOnce}; 5 | 6 | use combine::parser::byte::{byte, spaces}; 7 | use combine::parser::choice::{choice, optional}; 8 | use combine::parser::combinator::no_partial; 9 | use combine::parser::item::{one_of, satisfy_map}; 10 | use combine::parser::range; 11 | use combine::parser::repeat::{escaped, sep_by}; 12 | use combine::parser::sequence::between; 13 | 14 | #[derive(PartialEq, Debug)] 15 | pub enum Value { 16 | Number(f64), 17 | String(String), 18 | Bool(bool), 19 | Null, 20 | Object(HashMap), 21 | Array(Vec), 22 | } 23 | 24 | #[inline(always)] 25 | pub fn json_value<'a, I>() -> impl Parser + 'a 26 | where 27 | I: RangeStream + 'a, 28 | I::Error: ParseError, 29 | { 30 | spaces().with(json_value_()) 31 | } 32 | 33 | // We need to use `parser!` to break the recursive use of `value` to prevent the returned parser 34 | // from containing itself 35 | parser! { 36 | #[inline(always)] 37 | fn json_value_['a, I]()(I) -> Value 38 | where [ I: RangeStream + 'a ] 39 | { 40 | choice(( 41 | json_string().map(Value::String), 42 | object().map(Value::Object), 43 | array().map(Value::Array), 44 | number().map(Value::Number), 45 | lex(range::range(&b"false"[..]).map(|_| Value::Bool(false))), 46 | lex(range::range(&b"true"[..]).map(|_| Value::Bool(true))), 47 | lex(range::range(&b"null"[..]).map(|_| Value::Null)), 48 | )) 49 | } 50 | } 51 | 52 | fn object<'a, I>() -> impl Parser> + 'a 53 | where 54 | I: RangeStream + 'a, 55 | I::Error: ParseError, 56 | { 57 | let field = (json_string(), lex(byte(b':')), json_value_()).map(|t| (t.0, t.2)); 58 | let fields = sep_by(field, lex(byte(b','))); 59 | between(lex(byte(b'{')), lex(byte(b'}')), fields).expected("object") 60 | } 61 | 62 | fn array<'a, I>() -> impl Parser> + 'a 63 | where 64 | I: RangeStream + 'a, 65 | I::Error: ParseError, 66 | { 67 | between( 68 | lex(byte(b'[')), 69 | lex(byte(b']')), 70 | sep_by(json_value_(), lex(byte(b','))), 71 | ) 72 | .expected("array") 73 | } 74 | 75 | fn json_string<'a, I>() -> impl Parser + 'a 76 | where 77 | I: RangeStream + 'a, 78 | I::Error: ParseError, 79 | { 80 | let back_slash_byte = satisfy_map(|c| { 81 | Some(match c { 82 | b'"' => b'"', 83 | b'\\' => b'\\', 84 | b'/' => b'/', 85 | b'b' => '\u{0008}' as u8, 86 | b'f' => '\u{000c}' as u8, 87 | b'n' => b'\n', 88 | b'r' => b'\r', 89 | b't' => b'\t', 90 | _ => return None, 91 | }) 92 | }); 93 | let inner = range::recognize(escaped( 94 | range::take_while1(|b| b != b'\\' && b != b'"'), 95 | b'\\', 96 | back_slash_byte, 97 | )) 98 | .map(|s| std::str::from_utf8(s).unwrap().to_owned()); 99 | between(byte(b'"'), lex(byte(b'"')), inner).expected("string") 100 | } 101 | 102 | fn number<'a, I>() -> impl Parser + 'a 103 | where 104 | I: RangeStream + 'a, 105 | I::Error: ParseError, 106 | { 107 | no_partial( 108 | lex(range::recognize(no_partial(( 109 | optional(one_of("+-".bytes())), 110 | byte(b'0').or((digits(), optional((byte(b'.'), digits()))).map(|_| b'0')), 111 | optional(( 112 | (one_of("eE".bytes()), optional(one_of("+-".bytes()))), 113 | digits(), 114 | )), 115 | )))) 116 | .map(|s: &'a [u8]| std::str::from_utf8(s).unwrap().parse().unwrap()) 117 | .expected("number"), 118 | ) 119 | } 120 | 121 | fn digits<'a, I>() -> impl Parser + 'a 122 | where 123 | I: RangeStream + 'a, 124 | I::Error: ParseError, 125 | { 126 | range::take_while1(|b| b >= b'0' && b <= b'9') 127 | } 128 | 129 | fn lex<'a, P>(p: P) -> impl Parser 130 | where 131 | P: Parser, 132 | P::Input: RangeStream, 133 | ::Error: ParseError< 134 | ::Item, 135 | ::Range, 136 | ::Position, 137 | >, 138 | { 139 | no_partial(p.skip(range::take_while(|b| { 140 | b == b' ' || b == b'\t' || b == b'\r' || b == b'\n' 141 | }))) 142 | } 143 | -------------------------------------------------------------------------------- /examples/grmtools-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grmtools-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "grmtools-app" 7 | path = "app.rs" 8 | 9 | [build-dependencies] 10 | cfgrammar = "0.13" 11 | lrlex = "0.13" 12 | lrpar = "0.13" 13 | 14 | [dependencies] 15 | cfgrammar = "0.13" 16 | lrlex = "0.13" 17 | lrpar = "0.13" 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /examples/grmtools-app/app.rs: -------------------------------------------------------------------------------- 1 | use lrlex::lrlex_mod; 2 | use lrpar::lrpar_mod; 3 | use std::{env, fs}; 4 | 5 | lrlex_mod!("json.l"); 6 | lrpar_mod!("json.y"); 7 | 8 | mod json_val; 9 | 10 | fn main() { 11 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 12 | .expect("Failed to read file"); 13 | 14 | let lexerdef = json_l::lexerdef(); 15 | let lexer = lexerdef.lexer(&src); 16 | let (res, errs) = json_y::parse(&lexer); 17 | for e in errs { 18 | println!("{}", e.pp(&lexer, &json_y::token_epp)); 19 | } 20 | match res { 21 | Some(r) => { 22 | #[cfg(debug_assertions)] 23 | println!("{r:#?}"); 24 | #[cfg(not(debug_assertions))] 25 | let _ = std::hint::black_box(r); 26 | } 27 | None => panic!(), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/grmtools-app/build.rs: -------------------------------------------------------------------------------- 1 | use cfgrammar::yacc::YaccKind; 2 | use lrlex::CTLexerBuilder; 3 | use std::{env, path::PathBuf}; 4 | 5 | fn main() { 6 | CTLexerBuilder::new() 7 | .lrpar_config(|ctp| { 8 | ctp.yacckind(YaccKind::Grmtools) 9 | .grammar_path("json.y") 10 | .output_path( 11 | [env::var("OUT_DIR").unwrap().as_str(), "json.y.rs"] 12 | .iter() 13 | .collect::(), 14 | ) 15 | .mod_name("json_y") 16 | }) 17 | .lexer_path("json.l") 18 | .output_path( 19 | [env::var("OUT_DIR").unwrap().as_str(), "json.l.rs"] 20 | .iter() 21 | .collect::(), 22 | ) 23 | .mod_name("json_l") 24 | .build() 25 | .unwrap(); 26 | } 27 | -------------------------------------------------------------------------------- /examples/grmtools-app/json.l: -------------------------------------------------------------------------------- 1 | %% 2 | "[^"]*" "STRING" 3 | -?(0|([1-9][0-9]*))(\.[0-9]*)?([eE][-+]?[0-9]+)? "FLOAT" 4 | \[ "[" 5 | \] "]" 6 | \{ "{" 7 | \} "}" 8 | : ":" 9 | , "," 10 | false "FALSE" 11 | null "NULL" 12 | true "TRUE" 13 | [\n\r\t ]+ ; 14 | . "UNMATCHED" 15 | -------------------------------------------------------------------------------- /examples/grmtools-app/json.y: -------------------------------------------------------------------------------- 1 | %start Object 2 | %expect-unused Unmatched "UNMATCHED" 3 | 4 | %% 5 | 6 | Object -> Result>: 7 | "{" ObjectMembersOpt "}" { Ok(Value::Object(HashMap::from_iter($2?))) } 8 | ; 9 | 10 | ObjectMembersOpt -> Result, Box>: 11 | ObjectMembers { $1 } 12 | | { Ok(Vec::new()) } 13 | ; 14 | 15 | ObjectMembers -> Result, Box>: 16 | ObjectMembers "," ObjectMember { flatten($1, $3) } 17 | | ObjectMember { Ok(vec![$1?]) } 18 | ; 19 | 20 | ObjectMember -> Result<(String, Value), Box>: 21 | "STRING" ":" Member { 22 | let s = $lexer.span_str($1.unwrap().span()); 23 | Ok((s[1..s.len() - 1].to_owned(), $3?)) 24 | } 25 | ; 26 | 27 | Member -> Result>: 28 | "[" ArrayMembersOpt "]" { Ok(Value::Array($2?)) } 29 | | "FALSE" { Ok(Value::Boolean(false)) } 30 | | "FLOAT" { Ok(Value::Num($lexer.span_str($1?.span()).parse::().unwrap())) } 31 | | "NULL" { Ok(Value::Null) } 32 | | Object { $1 } 33 | | "STRING" { 34 | let s = $lexer.span_str($1.unwrap().span()); 35 | Ok(Value::Str(s[1..s.len() - 1].to_owned())) 36 | } 37 | | "TRUE" { Ok(Value::Boolean(true)) } 38 | ; 39 | 40 | ArrayMembersOpt -> Result, Box>: 41 | ArrayMembers { $1 } 42 | | { Ok(Vec::new()) } 43 | ; 44 | 45 | ArrayMembers -> Result, Box>: 46 | ArrayMembers "," Member { flatten($1, $3) } 47 | | Member { Ok(vec![$1?])} 48 | ; 49 | 50 | Unmatched -> (): 51 | "UNMATCHED" { } 52 | ; 53 | 54 | %% 55 | 56 | use crate::json_val::Value; 57 | use std::{collections::HashMap, error::Error}; 58 | 59 | fn flatten(lhs: Result, Box>, rhs: Result>) 60 | -> Result, Box> 61 | { 62 | let mut lhs = lhs?; 63 | let rhs = rhs?; 64 | lhs.push(rhs); 65 | Ok(lhs) 66 | } 67 | -------------------------------------------------------------------------------- /examples/grmtools-app/json_val.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub enum Value { 5 | Null, 6 | Boolean(bool), 7 | Str(String), 8 | Num(f64), 9 | Array(Vec), 10 | Object(HashMap), 11 | } 12 | -------------------------------------------------------------------------------- /examples/lalrpop-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lalrpop-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "lalrpop-app" 7 | path = "app.rs" 8 | 9 | [build-dependencies] 10 | lalrpop = { version = "0.22", features = ["lexer", "unicode"] } 11 | 12 | [dependencies] 13 | lalrpop-util = { version = "0.22", features = ["lexer", "unicode"] } 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /examples/lalrpop-app/app.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop_util; 2 | 3 | use std::env; 4 | use std::fs; 5 | 6 | #[rustfmt::skip] 7 | #[allow(clippy::all)] 8 | mod json; 9 | mod json_val; 10 | 11 | fn main() { 12 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 13 | .expect("Failed to read file"); 14 | 15 | match json::ValueParser::new().parse(&src) { 16 | Ok(json) => { 17 | #[cfg(debug_assertions)] 18 | { 19 | println!("{:#?}", json); 20 | } 21 | #[cfg(not(debug_assertions))] 22 | { 23 | std::hint::black_box(json); 24 | } 25 | } 26 | Err(err) => { 27 | eprintln!("{}", err); 28 | std::process::exit(1); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/lalrpop-app/build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | 3 | fn main() { 4 | lalrpop::process_root().unwrap() 5 | } 6 | -------------------------------------------------------------------------------- /examples/lalrpop-app/foo.rs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env cargo 2 | 3 | #![cargo(r#" 4 | [dependencies] 5 | foo = "1.2.3" 6 | "#)] 7 | 8 | 9 | 10 | 11 | #!/usr/bin/env cargo 12 | 13 | /*!```cargo 14 | [dependencies] 15 | foo = "1.2.3" 16 | */ 17 | 18 | 19 | 20 | #!/usr/bin/env cargo 21 | 22 | //! ```cargo 23 | //! [dependencies] 24 | //! foo = "1.2.3" 25 | //! ``` 26 | 27 | 28 | 29 | #!/usr/bin/env cargo 30 | --- 31 | [dependencies] 32 | foo = "1.2.3" 33 | --- 34 | 35 | 36 | 37 | #!/usr/bin/env cargo 38 | #[cargo(version = "1.2.3")] 39 | extern crate foo; 40 | 41 | 42 | 43 | 44 | 45 | 46 | - Module level: nice to have at top 47 | - Attribute has a lot of concepts if we have to do string 48 | - 49 | 50 | 51 | In educational material, a comment for dependencies likely is already being used, so this is no different 52 | 53 | custom class to hide the code block 54 | "hidden" attribute in infostring 55 | 56 | attributes are a "here be dragons" 57 | 58 | 59 | "I don't event teach attributes until the second course" 60 | - Nathan 61 | 62 | 63 | Disable `mod foo;`? 64 | 65 | 66 | doc-comment 67 | - easy to understand when seen 68 | - but concern from Nathan on teaching people to write it from scratch 69 | -------------------------------------------------------------------------------- /examples/lalrpop-app/json.lalrpop: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::str::FromStr; 3 | use crate::json_val::Value; 4 | 5 | grammar; 6 | 7 | // https://datatracker.ietf.org/doc/html/rfc7159 8 | 9 | pub Value: Value = { 10 | Object => Value::Object(<>), 11 | Array => Value::Array(<>), 12 | Number => Value::Num(<>), 13 | String => Value::Str(<>), 14 | "false" => Value::Boolean(false), 15 | "null" => Value::Null, 16 | "true" => Value::Boolean(true), 17 | }; 18 | 19 | Object: HashMap = { 20 | "{" > "}" => HashMap::from_iter(<>) 21 | }; 22 | 23 | Member: (String, Value) = { 24 | ":" => (s,v), 25 | }; 26 | 27 | Array: Vec = { 28 | "[" > "]", 29 | }; 30 | 31 | Number: f64 = { 32 | r"-?(0|([1-9][0-9]*))(\.[0-9]*)?([eE][-+]?[0-9]+)?" => f64::from_str(<>).unwrap() 33 | }; 34 | 35 | String: String = { 36 | r#""[^"]*""# => <>.into(), 37 | }; 38 | 39 | Comma: Vec = { 40 | ",")*> => { 41 | v.into_iter().chain(e).collect() 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /examples/lalrpop-app/json_val.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub enum Value { 5 | Null, 6 | Boolean(bool), 7 | Str(String), 8 | Num(f64), 9 | Array(Vec), 10 | Object(HashMap), 11 | } 12 | -------------------------------------------------------------------------------- /examples/lelwel-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lelwel-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "lelwel-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | codespan-reporting = "0.12.0" 11 | logos = "0.15.0" 12 | 13 | [dev-dependencies] 14 | lelwel = "0.8.0" 15 | snapbox = "0.6.21" 16 | 17 | [lints] 18 | workspace = true 19 | -------------------------------------------------------------------------------- /examples/lelwel-app/app.rs: -------------------------------------------------------------------------------- 1 | mod lexer; 2 | mod parser; 3 | 4 | use std::{env, fs}; 5 | 6 | use codespan_reporting::diagnostic::Severity; 7 | use codespan_reporting::files::SimpleFile; 8 | use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; 9 | use codespan_reporting::term::{self, Config}; 10 | 11 | fn main() { 12 | let path = env::args().nth(1).expect("Expected file argument"); 13 | let src = fs::read_to_string(&path).expect("Failed to read file"); 14 | 15 | let mut diags = vec![]; 16 | let cst = parser::Parser::parse(&src, &mut diags); 17 | 18 | #[cfg(debug_assertions)] 19 | { 20 | println!("{}", cst); 21 | } 22 | #[cfg(not(debug_assertions))] 23 | { 24 | std::hint::black_box(cst); 25 | } 26 | 27 | if !diags.is_empty() { 28 | let writer = StandardStream::stderr(ColorChoice::Auto); 29 | let config = Config::default(); 30 | let file = SimpleFile::new(&path, &src); 31 | for diag in diags.iter() { 32 | term::emit(&mut writer.lock(), &config, &file, diag).unwrap(); 33 | } 34 | if diags.iter().any(|d| d.severity == Severity::Error) { 35 | std::process::exit(1); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/lelwel-app/generated.rs: -------------------------------------------------------------------------------- 1 | // generated by lelwel 0.8.0 2 | 3 | macro_rules! syntax_error_message { 4 | [] => { 5 | "invalid syntax".to_string() 6 | }; 7 | [$($tk:literal),+] => { 8 | { 9 | let expected = [$($tk),*]; 10 | let mut msg = "invalid syntax, expected".to_string(); 11 | if expected.len() > 1 { 12 | msg.push_str(" one of: "); 13 | } else { 14 | msg.push_str(": "); 15 | } 16 | let mut count = 0; 17 | for e in expected { 18 | count += 1; 19 | let s = format!("{}", e); 20 | let s = if s.starts_with('<') && s.ends_with('>') && s.len() > 2 { 21 | s 22 | } else { 23 | format!("'{}'", s) 24 | }; 25 | msg.push_str(&s); 26 | if count < expected.len() { 27 | msg.push_str(", "); 28 | } 29 | } 30 | msg 31 | } 32 | } 33 | } 34 | macro_rules! err { 35 | [$self:expr, $($tk:literal),*] => { 36 | $self.create_diagnostic($self.span(), syntax_error_message!($($tk),*)) 37 | } 38 | } 39 | 40 | #[derive(Copy, Clone, PartialEq, Eq)] 41 | #[allow(dead_code)] 42 | pub enum Rule { 43 | Array, 44 | Error, 45 | File, 46 | Literal, 47 | Member, 48 | Object, 49 | Value, 50 | } 51 | 52 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] 53 | pub struct NodeRef(pub usize); 54 | 55 | impl NodeRef { 56 | #[allow(dead_code)] 57 | pub const ROOT: NodeRef = NodeRef(0); 58 | } 59 | 60 | #[cfg(target_pointer_width = "64")] 61 | #[derive(Debug, Copy, Clone)] 62 | pub struct CstIndex([u8; 6]); 63 | 64 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 65 | #[derive(Debug, Copy, Clone)] 66 | pub struct CstIndex(usize); 67 | 68 | impl From for usize { 69 | #[cfg(target_pointer_width = "64")] 70 | #[inline] 71 | fn from(value: CstIndex) -> Self { 72 | let [b0, b1, b2, b3, b4, b5] = value.0; 73 | usize::from_le_bytes([b0, b1, b2, b3, b4, b5, 0, 0]) 74 | } 75 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 76 | #[inline] 77 | fn from(value: CstIndex) -> Self { 78 | value.0 79 | } 80 | } 81 | impl From for CstIndex { 82 | #[cfg(target_pointer_width = "64")] 83 | #[inline] 84 | fn from(value: usize) -> Self { 85 | let [b0, b1, b2, b3, b4, b5, b6, b7] = value.to_le_bytes(); 86 | debug_assert!(b6 == 0 && b7 == 0); 87 | Self([b0, b1, b2, b3, b4, b5]) 88 | } 89 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 90 | #[inline] 91 | fn from(value: usize) -> Self { 92 | Self(value) 93 | } 94 | } 95 | 96 | /// Type of a node in the CST. 97 | /// 98 | /// The nodes for rules contain the offset to their last child node. 99 | /// The nodes for tokens contain an index to their span. 100 | /// 101 | /// On 64 bit platforms offsets and indices are stored as 48 bit integers. 102 | /// This allows the `Node` type to be 8 bytes in size as long as the `Rule` 103 | /// and `Token` enums are one byte in size. 104 | #[derive(Debug, Copy, Clone)] 105 | pub enum Node { 106 | Rule(Rule, CstIndex), 107 | Token(Token, CstIndex), 108 | } 109 | 110 | #[derive(Clone, Copy)] 111 | struct MarkOpened(usize); 112 | #[derive(Clone, Copy)] 113 | struct MarkClosed(usize); 114 | #[derive(Clone)] 115 | struct MarkTruncation { 116 | node_count: usize, 117 | token_count: usize, 118 | non_skip_len: usize, 119 | } 120 | 121 | /// An iterator for child nodes of a CST node. 122 | pub struct CstChildren<'a> { 123 | iter: std::slice::Iter<'a, Node>, 124 | offset: usize, 125 | } 126 | impl Iterator for CstChildren<'_> { 127 | type Item = NodeRef; 128 | 129 | fn next(&mut self) -> Option { 130 | let offset = self.offset; 131 | self.offset += 1; 132 | if let Some(node) = self.iter.next() { 133 | if let Node::Rule(_, end_offset) = node { 134 | let end_offset = usize::from(*end_offset); 135 | if end_offset > 0 { 136 | self.iter.nth(end_offset.saturating_sub(1)); 137 | self.offset += end_offset; 138 | } 139 | } 140 | Some(NodeRef(offset)) 141 | } else { 142 | None 143 | } 144 | } 145 | } 146 | 147 | pub type Span = core::ops::Range; 148 | 149 | /// A concrete syntax tree (CST) type. 150 | /// 151 | /// Nodes are laid out linearly in memory. 152 | /// Spans for tokens are directly stored in the `spans` vector. 153 | /// Spans for rule nodes are calculated based on their contained token nodes. 154 | /// 155 | /// # Example 156 | /// This syntax tree 157 | /// ```text 158 | /// foo 159 | /// bar 160 | /// A 161 | /// B 162 | /// C 163 | /// ``` 164 | /// will have the following `nodes` vector. 165 | /// ```text 166 | /// [ 167 | /// Node::Rule(Rule::Foo, 4), 168 | /// Node::Rule(Rule::Bar, 2), 169 | /// Node::Token(Token::A, 0), 170 | /// Node::Token(Token::B, 1), 171 | /// Node::Token(Token::C, 2), 172 | /// ] 173 | /// ``` 174 | pub struct Cst<'a> { 175 | source: &'a str, 176 | spans: Vec, 177 | nodes: Vec, 178 | token_count: usize, 179 | non_skip_len: usize, 180 | } 181 | #[allow(dead_code)] 182 | impl<'a> Cst<'a> { 183 | fn new(source: &'a str, spans: Vec) -> Self { 184 | let nodes = Vec::with_capacity(spans.len() * 2); 185 | Self { 186 | source, 187 | spans, 188 | nodes, 189 | token_count: 0, 190 | non_skip_len: 0, 191 | } 192 | } 193 | fn open(&mut self) -> MarkOpened { 194 | let mark = MarkOpened(self.nodes.len()); 195 | self.nodes.push(Node::Rule(Rule::Error, 0.into())); 196 | self.non_skip_len = self.nodes.len(); 197 | mark 198 | } 199 | fn close(&mut self, mark: MarkOpened, rule: Rule) -> MarkClosed { 200 | let len = self.non_skip_len - 1; 201 | self.nodes[mark.0] = Node::Rule(rule, if mark.0 > len { 202 | self.non_skip_len += mark.0 - len; 203 | 0 204 | } else { 205 | len - mark.0 206 | }.into()); 207 | MarkClosed(mark.0) 208 | } 209 | fn close_root(&mut self, mark: MarkOpened, rule: Rule) -> MarkClosed { 210 | self.nodes[mark.0] = Node::Rule(rule, (self.nodes.len() - 1 - mark.0).into()); 211 | MarkClosed(mark.0) 212 | } 213 | fn advance(&mut self, token: Token, skip: bool) { 214 | self.nodes.push(Node::Token(token, self.token_count.into())); 215 | self.token_count += 1; 216 | if !skip { 217 | self.non_skip_len = self.nodes.len(); 218 | } 219 | } 220 | fn open_before(&mut self, mark: MarkClosed) -> MarkOpened { 221 | self.nodes.insert(mark.0, Node::Rule(Rule::Error, 0.into())); 222 | self.non_skip_len += 1; 223 | MarkOpened(mark.0) 224 | } 225 | fn mark(&self) -> MarkClosed { 226 | MarkClosed(self.nodes.len()) 227 | } 228 | fn mark_truncation(&self) -> MarkTruncation { 229 | MarkTruncation { 230 | node_count: self.nodes.len(), 231 | token_count: self.token_count, 232 | non_skip_len: self.non_skip_len, 233 | } 234 | } 235 | fn truncate(&mut self, mark: MarkTruncation) { 236 | self.nodes.truncate(mark.node_count); 237 | self.token_count = mark.token_count; 238 | self.non_skip_len = mark.non_skip_len; 239 | } 240 | /// Returns an iterator over the children of the node referenced by `node_ref`. 241 | pub fn children(&self, node_ref: NodeRef) -> CstChildren { 242 | let iter = if let Node::Rule(_, end_offset) = self.nodes[node_ref.0] { 243 | self.nodes[node_ref.0 + 1..node_ref.0 + usize::from(end_offset) + 1].iter() 244 | } else { 245 | std::slice::Iter::default() 246 | }; 247 | CstChildren { 248 | iter, 249 | offset: node_ref.0 + 1, 250 | } 251 | } 252 | /// Returns the node referenced by `node_ref`. 253 | pub fn get(&self, node_ref: NodeRef) -> Node { 254 | self.nodes[node_ref.0] 255 | } 256 | /// Returns the span for the node referenced by `node_ref`. 257 | /// 258 | /// For rules the span is calculated based on the first and last token. 259 | /// If there are no tokens the function returns `None`. 260 | pub fn span(&self, node_ref: NodeRef) -> Span { 261 | fn find_token<'a>(mut iter: impl Iterator) -> Option { 262 | iter.find_map(|node| match node { 263 | Node::Rule(..) => None, 264 | Node::Token(_, idx) => Some(usize::from(*idx)), 265 | }) 266 | } 267 | match self.nodes[node_ref.0] { 268 | Node::Token(_, idx) => self.spans[usize::from(idx)].clone(), 269 | Node::Rule(_, end_offset) => { 270 | let end = node_ref.0 + usize::from(end_offset); 271 | let first = find_token(self.nodes[node_ref.0 + 1..=end].iter()); 272 | let last = find_token(self.nodes[node_ref.0 + 1..=end].iter().rev()); 273 | if let (Some(first), Some(last)) = (first, last) { 274 | self.spans[first].start..self.spans[last].end 275 | } else { 276 | let offset = find_token(self.nodes[..node_ref.0].iter().rev()) 277 | .map_or(0, |before| self.spans[before].end); 278 | offset..offset 279 | } 280 | } 281 | } 282 | } 283 | /// Returns the slice and span of the node referenced by `node_ref` if it matches `matched_token`. 284 | pub fn match_token(&self, node_ref: NodeRef, matched_token: Token) -> Option<(&'a str, Span)> { 285 | match self.nodes[node_ref.0] { 286 | Node::Token(token, idx) if token == matched_token => { 287 | let span = &self.spans[usize::from(idx)]; 288 | Some((&self.source[span.clone()], span.clone())) 289 | } 290 | _ => None, 291 | } 292 | } 293 | /// Checks if the node referenced by `node_ref` matches `matched_rule`. 294 | pub fn match_rule(&self, node_ref: NodeRef, matched_rule: Rule) -> bool { 295 | matches!(self.nodes[node_ref.0], Node::Rule(rule, _) if rule == matched_rule) 296 | } 297 | } 298 | 299 | impl std::fmt::Display for Cst<'_> { 300 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 301 | const DEPTH: &str = " "; 302 | fn rec( 303 | cst: &Cst, 304 | f: &mut std::fmt::Formatter<'_>, 305 | node_ref: NodeRef, 306 | indent: usize, 307 | ) -> std::fmt::Result { 308 | match cst.get(node_ref) { 309 | Node::Rule(rule, _) => { 310 | let span = cst.span(node_ref); 311 | writeln!(f, "{}{rule:?} [{span:?}]", DEPTH.repeat(indent))?; 312 | for child_node_ref in cst.children(node_ref) { 313 | rec(cst, f, child_node_ref, indent + 1)?; 314 | } 315 | Ok(()) 316 | } 317 | Node::Token(token, idx) => { 318 | let span = &cst.spans[usize::from(idx)]; 319 | writeln!( 320 | f, 321 | "{}{:?} {:?} [{:?}]", 322 | DEPTH.repeat(indent), 323 | token, 324 | &cst.source[span.clone()], 325 | span, 326 | ) 327 | } 328 | } 329 | } 330 | rec(self, f, NodeRef::ROOT, 0) 331 | } 332 | } 333 | impl std::fmt::Debug for Rule { 334 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 335 | match self { 336 | Rule::Array => write!(f, "array"), 337 | Rule::Error => write!(f, "error"), 338 | Rule::File => write!(f, "file"), 339 | Rule::Literal => write!(f, "literal"), 340 | Rule::Member => write!(f, "member"), 341 | Rule::Object => write!(f, "object"), 342 | Rule::Value => write!(f, "value"), 343 | } 344 | } 345 | } 346 | 347 | macro_rules! expect { 348 | ($token:ident, $sym:literal, $self:expr, $diags:expr) => { 349 | if let Token::$token = $self.current { 350 | $self.advance(false); 351 | } else { 352 | $self.error($diags, err![$self, $sym]); 353 | } 354 | }; 355 | } 356 | #[allow(unused_macros)] 357 | macro_rules! try_expect { 358 | ($token:ident, $sym:literal, $self:expr, $diags:expr) => { 359 | if let Token::$token = $self.current { 360 | $self.advance(false); 361 | } else { 362 | if $self.in_ordered_choice { 363 | return None; 364 | } 365 | $self.error($diags, err![$self, $sym]); 366 | } 367 | }; 368 | } 369 | 370 | struct ParserState { 371 | pos: usize, 372 | current: Token, 373 | truncation_mark: MarkTruncation, 374 | diag_count: usize, 375 | } 376 | pub struct Parser<'a> { 377 | cst: Cst<'a>, 378 | tokens: Vec, 379 | pos: usize, 380 | current: Token, 381 | last_error_span: Span, 382 | max_offset: usize, 383 | #[allow(dead_code)] 384 | context: Context<'a>, 385 | error_cooldown: bool, 386 | #[allow(dead_code)] 387 | in_ordered_choice: bool, 388 | } 389 | #[allow(clippy::while_let_loop, dead_code, unused_parens)] 390 | impl<'a> Parser<'a> { 391 | fn error(&mut self, diags: &mut Vec, diag: Diagnostic) { 392 | if self.error_cooldown || self.last_error_span == self.span() { 393 | return; 394 | } 395 | self.last_error_span = self.span(); 396 | diags.push(diag); 397 | } 398 | fn advance(&mut self, error: bool) { 399 | if !error { 400 | self.error_cooldown = false; 401 | } 402 | self.cst.advance(self.current, false); 403 | loop { 404 | self.pos += 1; 405 | match self.tokens.get(self.pos) { 406 | Some(token @ (Token::Error | Token::Whitespace)) => { 407 | self.cst.advance(*token, true); 408 | continue; 409 | } 410 | Some(token) => { 411 | self.current = *token; 412 | break; 413 | } 414 | None => { 415 | self.current = Token::EOF; 416 | break; 417 | } 418 | } 419 | } 420 | } 421 | fn is_skipped(token: Token) -> bool { 422 | matches!(token, Token::Error | Token::Whitespace) 423 | } 424 | fn init_skip(&mut self) { 425 | loop { 426 | match self.tokens.get(self.pos) { 427 | Some(token @ (Token::Error | Token::Whitespace)) => { 428 | self.pos += 1; 429 | self.cst.advance(*token, true); 430 | continue; 431 | } 432 | Some(token) => { 433 | self.current = *token; 434 | break; 435 | } 436 | None => { 437 | self.current = Token::EOF; 438 | break; 439 | } 440 | } 441 | } 442 | } 443 | fn advance_with_error(&mut self, diags: &mut Vec, diag: Diagnostic) { 444 | let m = self.cst.open(); 445 | self.error(diags, diag); 446 | self.error_cooldown = true; 447 | self.advance(true); 448 | self.cst.close(m, Rule::Error); 449 | self.create_node_error(NodeRef(m.0), diags); 450 | } 451 | fn peek(&self, lookahead: usize) -> Token { 452 | self.tokens 453 | .iter() 454 | .skip(self.pos) 455 | .filter(|token| !Self::is_skipped(**token)) 456 | .nth(lookahead) 457 | .map_or(Token::EOF, |it| *it) 458 | } 459 | fn peek_left(&self, lookbehind: usize) -> Token { 460 | self.tokens 461 | .iter() 462 | .take(self.pos + 1) 463 | .rev() 464 | .filter(|token| !Self::is_skipped(**token)) 465 | .nth(lookbehind) 466 | .map_or(Token::EOF, |it| *it) 467 | } 468 | fn span(&self) -> Span { 469 | self.cst.spans 470 | .get(self.pos) 471 | .map_or(self.max_offset..self.max_offset, |span| span.clone()) 472 | } 473 | fn get_state(&self, diags: &[Diagnostic]) -> ParserState { 474 | ParserState { 475 | pos: self.pos, 476 | current: self.current, 477 | truncation_mark: self.cst.mark_truncation(), 478 | diag_count: diags.len(), 479 | } 480 | } 481 | fn set_state(&mut self, state: &ParserState, diags: &mut Vec) { 482 | self.pos = state.pos; 483 | self.current = state.current; 484 | diags.truncate(state.diag_count); 485 | for i in state.truncation_mark.node_count..self.cst.nodes.len() { 486 | if let Node::Rule(rule, _) = self.cst.nodes[i] { 487 | self.delete_node(rule, NodeRef(i)); 488 | } 489 | } 490 | self.cst.truncate(state.truncation_mark.clone()); 491 | } 492 | fn create_node(&mut self, rule: Rule, node_ref: NodeRef, diags: &mut Vec) { 493 | match rule { 494 | Rule::Array => self.create_node_array(node_ref, diags), 495 | Rule::Error => self.create_node_error(node_ref, diags), 496 | Rule::File => self.create_node_file(node_ref, diags), 497 | Rule::Literal => self.create_node_literal(node_ref, diags), 498 | Rule::Member => self.create_node_member(node_ref, diags), 499 | Rule::Object => self.create_node_object(node_ref, diags), 500 | Rule::Value => self.create_node_value(node_ref, diags), 501 | } 502 | } 503 | fn delete_node(&mut self, _rule: Rule, _node_ref: NodeRef) { 504 | 505 | } 506 | /// Returns the CST for a parse with the given `source` file and writes diagnostics to `diags`. 507 | /// 508 | /// The context can be explicitly defined for the parse. 509 | pub fn parse_with_context( 510 | source: &'a str, 511 | diags: &mut Vec, 512 | context: Context<'a>, 513 | ) -> Cst<'a> { 514 | let (tokens, spans) = Self::create_tokens(source, diags); 515 | let max_offset = source.len(); 516 | let mut parser = Self { 517 | current: Token::EOF, 518 | cst: Cst::new(source, spans), 519 | tokens, 520 | pos: 0, 521 | last_error_span: Span::default(), 522 | max_offset, 523 | context, 524 | error_cooldown: false, 525 | in_ordered_choice: false, 526 | }; 527 | parser.rule_file(diags); 528 | parser.cst 529 | } 530 | /// Returns the CST for a parse with the given `source` file and writes diagnostics to `diags`. 531 | /// 532 | /// The context will be default initialized for the parse. 533 | pub fn parse( 534 | source: &'a str, 535 | diags: &mut Vec, 536 | ) -> Cst<'a> { 537 | Self::parse_with_context(source, diags, Context::default()) 538 | } 539 | fn rule_file(&mut self, diags: &mut Vec) { 540 | let m = self.cst.open(); 541 | self.init_skip(); 542 | self.rule_value(diags); 543 | if self.current != Token::EOF { 544 | self.error(diags, err![self, ""]); 545 | let error_tree = self.cst.open(); 546 | loop { 547 | match self.tokens.get(self.pos) { 548 | None => break, 549 | Some(token) => self.cst.advance(*token, Self::is_skipped(*token)), 550 | } 551 | self.pos += 1; 552 | } 553 | self.cst.close(error_tree, Rule::Error); 554 | self.create_node_error(NodeRef(error_tree.0), diags); 555 | } 556 | let closed = self.cst.close_root(m, Rule::File); 557 | self.create_node_file(NodeRef(closed.0), diags); 558 | 559 | } 560 | fn rule_value(&mut self, diags: &mut Vec) { 561 | match self.current { 562 | Token::LBrace => { 563 | self.rule_object(diags); 564 | } 565 | Token::LBrak => { 566 | self.rule_array(diags); 567 | } 568 | Token::False 569 | | Token::Null 570 | | Token::Number 571 | | Token::String 572 | | Token::True => { 573 | self.rule_literal(diags); 574 | } 575 | _ => { 576 | self.error(diags, err![self, "false", 577 | "{", 578 | "[", 579 | "null", 580 | "", 581 | "", 582 | "true"]); 583 | } 584 | } 585 | } 586 | fn rule_object(&mut self, diags: &mut Vec) { 587 | let m = self.cst.open(); 588 | expect!(LBrace, "{", self, diags); 589 | match self.current { 590 | Token::String => { 591 | self.rule_member(diags); 592 | loop { 593 | match self.current { 594 | Token::Comma => { 595 | expect!(Comma, ",", self, diags); 596 | self.rule_member(diags); 597 | } 598 | Token::RBrace 599 | | Token::EOF 600 | | Token::RBrak => break, 601 | _ => { 602 | self.advance_with_error(diags, err![self, ",", 603 | "}"]); 604 | } 605 | } 606 | } 607 | } 608 | Token::RBrace => {} 609 | _ => { 610 | self.error(diags, err![self, "}", 611 | ""]); 612 | } 613 | } 614 | expect!(RBrace, "}", self, diags); 615 | let closed = self.cst.close(m, Rule::Object); 616 | self.create_node_object(NodeRef(closed.0), diags); 617 | 618 | } 619 | fn rule_member(&mut self, diags: &mut Vec) { 620 | let m = self.cst.open(); 621 | expect!(String, "", self, diags); 622 | expect!(Colon, ":", self, diags); 623 | self.rule_value(diags); 624 | let closed = self.cst.close(m, Rule::Member); 625 | self.create_node_member(NodeRef(closed.0), diags); 626 | 627 | } 628 | fn rule_array(&mut self, diags: &mut Vec) { 629 | let m = self.cst.open(); 630 | expect!(LBrak, "[", self, diags); 631 | match self.current { 632 | Token::False 633 | | Token::LBrace 634 | | Token::LBrak 635 | | Token::Null 636 | | Token::Number 637 | | Token::String 638 | | Token::True => { 639 | self.rule_value(diags); 640 | loop { 641 | match self.current { 642 | Token::Comma => { 643 | expect!(Comma, ",", self, diags); 644 | self.rule_value(diags); 645 | } 646 | Token::RBrak 647 | | Token::EOF 648 | | Token::RBrace => break, 649 | _ => { 650 | self.advance_with_error(diags, err![self, ",", 651 | "]"]); 652 | } 653 | } 654 | } 655 | } 656 | Token::RBrak => {} 657 | _ => { 658 | self.error(diags, err![self, "false", 659 | "{", 660 | "[", 661 | "null", 662 | "", 663 | "]", 664 | "", 665 | "true"]); 666 | } 667 | } 668 | expect!(RBrak, "]", self, diags); 669 | let closed = self.cst.close(m, Rule::Array); 670 | self.create_node_array(NodeRef(closed.0), diags); 671 | 672 | } 673 | fn rule_literal(&mut self, diags: &mut Vec) { 674 | let m = self.cst.open(); 675 | match self.current { 676 | Token::String => { 677 | expect!(String, "", self, diags); 678 | } 679 | Token::Number => { 680 | expect!(Number, "", self, diags); 681 | } 682 | Token::True => { 683 | expect!(True, "true", self, diags); 684 | } 685 | Token::False => { 686 | expect!(False, "false", self, diags); 687 | } 688 | Token::Null => { 689 | expect!(Null, "null", self, diags); 690 | } 691 | _ => { 692 | self.error(diags, err![self, "false", 693 | "null", 694 | "", 695 | "", 696 | "true"]); 697 | } 698 | } 699 | let closed = self.cst.close(m, Rule::Literal); 700 | self.create_node_literal(NodeRef(closed.0), diags); 701 | 702 | } 703 | } 704 | 705 | #[allow(clippy::ptr_arg)] 706 | trait ParserCallbacks { 707 | /// Called at the start of the parse to generate all tokens and corresponding spans. 708 | fn create_tokens(source: &str, diags: &mut Vec) -> (Vec, Vec); 709 | /// Called when diagnostic is created. 710 | fn create_diagnostic(&self, span: Span, message: String) -> Diagnostic; 711 | 712 | /// Called when `array` node is created. 713 | fn create_node_array(&mut self, _node_ref: NodeRef, _diags: &mut Vec) {} 714 | /// Called when `error` node is created. 715 | fn create_node_error(&mut self, _node_ref: NodeRef, _diags: &mut Vec) {} 716 | /// Called when `file` node is created. 717 | fn create_node_file(&mut self, _node_ref: NodeRef, _diags: &mut Vec) {} 718 | /// Called when `literal` node is created. 719 | fn create_node_literal(&mut self, _node_ref: NodeRef, _diags: &mut Vec) {} 720 | /// Called when `member` node is created. 721 | fn create_node_member(&mut self, _node_ref: NodeRef, _diags: &mut Vec) {} 722 | /// Called when `object` node is created. 723 | fn create_node_object(&mut self, _node_ref: NodeRef, _diags: &mut Vec) {} 724 | /// Called when `value` node is created. 725 | fn create_node_value(&mut self, _node_ref: NodeRef, _diags: &mut Vec) {} 726 | 727 | 728 | } 729 | -------------------------------------------------------------------------------- /examples/lelwel-app/json.llw: -------------------------------------------------------------------------------- 1 | token True='true' False='false' Null='null'; 2 | token LBrace='{' RBrace='}' LBrak='[' RBrak=']' Comma=',' Colon=':'; 3 | token String='' Number=''; 4 | token Whitespace; 5 | 6 | skip Whitespace; 7 | 8 | start file; 9 | 10 | file: value; 11 | value^: 12 | object 13 | | array 14 | | literal 15 | ; 16 | object: '{' [member (',' member)*] '}'; 17 | member: String ':' value; 18 | array: '[' [value (',' value)*] ']'; 19 | literal: 20 | String 21 | | Number 22 | | 'true' 23 | | 'false' 24 | | 'null' 25 | ; 26 | -------------------------------------------------------------------------------- /examples/lelwel-app/lexer.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::{Diagnostic, Span}; 2 | use codespan_reporting::diagnostic::Label; 3 | use logos::{Lexer, Logos}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Default)] 6 | pub enum LexerError { 7 | #[default] 8 | Invalid, 9 | UnterminatedString, 10 | } 11 | 12 | impl LexerError { 13 | pub fn into_diagnostic(self, span: Span) -> Diagnostic { 14 | match self { 15 | Self::Invalid => Diagnostic::error() 16 | .with_message("invalid token") 17 | .with_labels(vec![Label::primary((), span)]), 18 | Self::UnterminatedString => Diagnostic::error() 19 | .with_message("unterminated string") 20 | .with_labels(vec![Label::primary((), span)]), 21 | } 22 | } 23 | } 24 | 25 | fn parse_string(lexer: &mut Lexer<'_, Token>) -> Result<(), LexerError> { 26 | let mut it = lexer.remainder().chars(); 27 | while let Some(c) = it.next() { 28 | match c { 29 | '"' => { 30 | lexer.bump(1); 31 | return Ok(()); 32 | } 33 | '\\' => { 34 | lexer.bump(1); 35 | if let Some(c) = it.next() { 36 | lexer.bump(c.len_utf8()); 37 | } 38 | } 39 | c => { 40 | lexer.bump(c.len_utf8()); 41 | } 42 | } 43 | } 44 | Err(LexerError::UnterminatedString) 45 | } 46 | 47 | #[allow(clippy::upper_case_acronyms)] 48 | #[derive(Logos, Debug, PartialEq, Copy, Clone)] 49 | #[logos(error = LexerError)] 50 | pub enum Token { 51 | EOF, 52 | #[regex("[\u{0020}\u{000A}\u{000D}\u{0009}]+")] 53 | Whitespace, 54 | #[token("true")] 55 | True, 56 | #[token("false")] 57 | False, 58 | #[token("null")] 59 | Null, 60 | #[token("{")] 61 | LBrace, 62 | #[token("}")] 63 | RBrace, 64 | #[token("[")] 65 | LBrak, 66 | #[token("]")] 67 | RBrak, 68 | #[token(",")] 69 | Comma, 70 | #[token(":")] 71 | Colon, 72 | #[regex("\"", parse_string)] 73 | String, 74 | #[regex(r"-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?")] 75 | Number, 76 | #[regex(r"[a-zA-Z][a-zA-Z0-9]*", |_| false)] 77 | Error, 78 | } 79 | 80 | fn check_string(value: &str, span: &Span, diags: &mut Vec) { 81 | let mut it = value.chars().enumerate(); 82 | while let Some((i, c)) = it.next() { 83 | match c { 84 | '\\' => match it.next() { 85 | Some((_, '"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't')) => {} 86 | Some((i, 'u')) => { 87 | for j in 0..4 { 88 | if !it 89 | .next() 90 | .map(|(_, c)| c.is_ascii_hexdigit()) 91 | .unwrap_or(false) 92 | { 93 | diags.push( 94 | Diagnostic::error() 95 | .with_message("invalid unicode escape sequence") 96 | .with_labels(vec![Label::primary( 97 | (), 98 | span.start + i - 1..span.start + i + j + 1, 99 | )]), 100 | ); 101 | break; 102 | } 103 | } 104 | } 105 | Some((j, _)) => { 106 | diags.push( 107 | Diagnostic::error() 108 | .with_message("invalid escape sequence") 109 | .with_labels(vec![Label::primary( 110 | (), 111 | span.start + j - 1..span.start + j + 1, 112 | )]), 113 | ); 114 | } 115 | _ => unreachable!(), 116 | }, 117 | '\u{0020}'..='\u{10FFFF}' => {} 118 | c => { 119 | diags.push( 120 | Diagnostic::error() 121 | .with_message(format!("string contains invalid character {:?}", c)) 122 | .with_labels(vec![Label::primary((), span.start + i..span.start + i + 1) 123 | .with_message("after this character")]), 124 | ); 125 | } 126 | } 127 | } 128 | } 129 | 130 | pub fn tokenize(source: &str, diags: &mut Vec) -> (Vec, Vec) { 131 | let lexer = Token::lexer(source); 132 | let mut tokens = vec![]; 133 | let mut spans = vec![]; 134 | let source = lexer.source(); 135 | 136 | let mut count_brace = 0; 137 | let mut count_brak = 0; 138 | for (token, span) in lexer.spanned() { 139 | match token { 140 | Ok(token) => { 141 | match token { 142 | Token::String => { 143 | check_string(&source[span.start..span.end], &span, diags); 144 | } 145 | Token::LBrace => count_brace += 1, 146 | Token::RBrace => count_brace -= 1, 147 | Token::LBrak => count_brak += 1, 148 | Token::RBrak => count_brak -= 1, 149 | _ => {} 150 | } 151 | if count_brace + count_brak > 256 { 152 | diags.push( 153 | Diagnostic::error() 154 | .with_message("bracket nesting level exceeded maximum of 256") 155 | .with_labels(vec![Label::primary((), span)]), 156 | ); 157 | break; 158 | } 159 | tokens.push(token); 160 | } 161 | Err(err) => { 162 | diags.push(err.into_diagnostic(span.clone())); 163 | tokens.push(Token::Error); 164 | } 165 | } 166 | spans.push(span); 167 | } 168 | (tokens, spans) 169 | } 170 | -------------------------------------------------------------------------------- /examples/lelwel-app/parser.rs: -------------------------------------------------------------------------------- 1 | use codespan_reporting::diagnostic::Label; 2 | 3 | use crate::lexer::{tokenize, Token}; 4 | 5 | pub type Diagnostic = codespan_reporting::diagnostic::Diagnostic<()>; 6 | 7 | #[derive(Default)] 8 | pub struct Context<'a> { 9 | marker: std::marker::PhantomData<&'a ()>, 10 | } 11 | 12 | include!("generated.rs"); 13 | 14 | impl ParserCallbacks for Parser<'_> { 15 | fn create_tokens(source: &str, diags: &mut Vec) -> (Vec, Vec) { 16 | tokenize(source, diags) 17 | } 18 | fn create_diagnostic(&self, span: Span, message: String) -> Diagnostic { 19 | Diagnostic::error() 20 | .with_message(message) 21 | .with_labels(vec![Label::primary((), span)]) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/lelwel-app/tests/codegen.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn codegen() { 3 | use snapbox::assert_data_eq; 4 | use snapbox::Data; 5 | 6 | let tmp_dir = env!("CARGO_TARGET_TMPDIR"); 7 | let mut output_dir = std::path::PathBuf::from(tmp_dir); 8 | output_dir.push("lelwel"); 9 | std::fs::create_dir_all(&output_dir).unwrap(); 10 | 11 | lelwel::compile( 12 | "json.llw", 13 | output_dir.as_os_str().to_str().unwrap(), 14 | false, 15 | 1, 16 | false, 17 | false, 18 | ) 19 | .unwrap(); 20 | 21 | let expected_root = std::path::Path::new("."); 22 | for entry in std::fs::read_dir(&output_dir).unwrap() { 23 | let entry = entry.unwrap(); 24 | let actual_path = entry.path(); 25 | let actual_name = entry.file_name(); 26 | let actual = std::fs::read_to_string(&actual_path).unwrap(); 27 | let expected_path = expected_root.join(actual_name); 28 | assert_data_eq!(actual, Data::read_from(&expected_path, None)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/logos-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logos-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "logos-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | logos = "0.15.0" 11 | 12 | [lints] 13 | workspace = true 14 | -------------------------------------------------------------------------------- /examples/logos-app/app.rs: -------------------------------------------------------------------------------- 1 | mod parser; 2 | 3 | use std::{env, fs}; 4 | 5 | use logos::Logos as _; 6 | 7 | fn main() { 8 | let filename = env::args().nth(1).expect("Expected file argument"); 9 | let src = fs::read_to_string(&filename).expect("Failed to read file"); 10 | 11 | let mut lexer = parser::Token::lexer(src.as_str()); 12 | match parser::parse_value(&mut lexer) { 13 | Ok(json) => { 14 | #[cfg(debug_assertions)] 15 | { 16 | println!("{:#?}", json); 17 | } 18 | #[cfg(not(debug_assertions))] 19 | { 20 | std::hint::black_box(json); 21 | } 22 | } 23 | Err((msg, span)) => { 24 | eprintln!("{filename}:{span:?}: {msg}"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/logos-app/parser.rs: -------------------------------------------------------------------------------- 1 | //! JSON parser written in Rust, using Logos. 2 | //! 3 | //! If the file is a valid JSON value, it will be printed 4 | //! to the terminal using the debug format. 5 | //! 6 | //! Otherwise, an error will be printed with its location. 7 | //! 8 | //! Usage: 9 | //! cargo run --example json 10 | //! 11 | //! Example: 12 | //! cargo run --example json examples/example.json 13 | 14 | /* ANCHOR: all */ 15 | use logos::{Lexer, Logos, Span}; 16 | 17 | use std::collections::HashMap; 18 | 19 | type Error = (String, Span); 20 | 21 | type Result = std::result::Result; 22 | 23 | /* ANCHOR: tokens */ 24 | /// All meaningful JSON tokens. 25 | /// 26 | /// > NOTE: regexes for [`Token::Number`] and [`Token::String`] may not 27 | /// > catch all possible values, especially for strings. If you find 28 | /// > errors, please report them so that we can improve the regex. 29 | #[derive(Debug, Logos)] 30 | #[logos(skip r"[ \t\r\n\f]+")] 31 | pub enum Token { 32 | #[token("false", |_| false)] 33 | #[token("true", |_| true)] 34 | Bool(bool), 35 | 36 | #[token("{")] 37 | BraceOpen, 38 | 39 | #[token("}")] 40 | BraceClose, 41 | 42 | #[token("[")] 43 | BracketOpen, 44 | 45 | #[token("]")] 46 | BracketClose, 47 | 48 | #[token(":")] 49 | Colon, 50 | 51 | #[token(",")] 52 | Comma, 53 | 54 | #[token("null")] 55 | Null, 56 | 57 | #[regex(r"-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?", |lex| lex.slice().parse::().unwrap())] 58 | Number(f64), 59 | 60 | #[regex(r#""([^"\\]|\\["\\bnfrt]|u[a-fA-F0-9]{4})*""#, |lex| lex.slice().to_owned())] 61 | String(String), 62 | } 63 | /* ANCHOR_END: tokens */ 64 | 65 | /* ANCHOR: values */ 66 | /// Represent any valid JSON value. 67 | #[derive(Debug)] 68 | pub enum Value { 69 | /// null. 70 | Null, 71 | /// true or false. 72 | Bool(bool), 73 | /// Any floating point number. 74 | Number(f64), 75 | /// Any quoted string. 76 | String(String), 77 | /// An array of values 78 | Array(Vec), 79 | /// An dictionary mapping keys and values. 80 | Object(HashMap), 81 | } 82 | /* ANCHOR_END: values */ 83 | 84 | /* ANCHOR: value */ 85 | /// Parse a token stream into a JSON value. 86 | pub fn parse_value(lexer: &mut Lexer<'_, Token>) -> Result { 87 | if let Some(token) = lexer.next() { 88 | match token { 89 | Ok(Token::Bool(b)) => Ok(Value::Bool(b)), 90 | Ok(Token::BraceOpen) => parse_object(lexer), 91 | Ok(Token::BracketOpen) => parse_array(lexer), 92 | Ok(Token::Null) => Ok(Value::Null), 93 | Ok(Token::Number(n)) => Ok(Value::Number(n)), 94 | Ok(Token::String(s)) => Ok(Value::String(s)), 95 | _ => Err(( 96 | "unexpected token here (context: value)".to_owned(), 97 | lexer.span(), 98 | )), 99 | } 100 | } else { 101 | Err(("empty values are not allowed".to_owned(), lexer.span())) 102 | } 103 | } 104 | /* ANCHOR_END: value */ 105 | 106 | /* ANCHOR: array */ 107 | /// Parse a token stream into an array and return when 108 | /// a valid terminator is found. 109 | /// 110 | /// > NOTE: we assume '[' was consumed. 111 | fn parse_array(lexer: &mut Lexer<'_, Token>) -> Result { 112 | let mut array = Vec::new(); 113 | let span = lexer.span(); 114 | let mut awaits_comma = false; 115 | let mut awaits_value = false; 116 | 117 | while let Some(token) = lexer.next() { 118 | match token { 119 | Ok(Token::Bool(b)) if !awaits_comma => { 120 | array.push(Value::Bool(b)); 121 | awaits_value = false; 122 | } 123 | Ok(Token::BraceOpen) if !awaits_comma => { 124 | let object = parse_object(lexer)?; 125 | array.push(object); 126 | awaits_value = false; 127 | } 128 | Ok(Token::BracketOpen) if !awaits_comma => { 129 | let sub_array = parse_array(lexer)?; 130 | array.push(sub_array); 131 | awaits_value = false; 132 | } 133 | Ok(Token::BracketClose) if !awaits_value => return Ok(Value::Array(array)), 134 | Ok(Token::Comma) if awaits_comma => awaits_value = true, 135 | Ok(Token::Null) if !awaits_comma => { 136 | array.push(Value::Null); 137 | awaits_value = false 138 | } 139 | Ok(Token::Number(n)) if !awaits_comma => { 140 | array.push(Value::Number(n)); 141 | awaits_value = false; 142 | } 143 | Ok(Token::String(s)) if !awaits_comma => { 144 | array.push(Value::String(s)); 145 | awaits_value = false; 146 | } 147 | _ => { 148 | return Err(( 149 | "unexpected token here (context: array)".to_owned(), 150 | lexer.span(), 151 | )) 152 | } 153 | } 154 | awaits_comma = !awaits_value; 155 | } 156 | Err(("unmatched opening bracket defined here".to_owned(), span)) 157 | } 158 | /* ANCHOR_END: array */ 159 | 160 | /* ANCHOR: object */ 161 | /// Parse a token stream into an object and return when 162 | /// a valid terminator is found. 163 | /// 164 | /// > NOTE: we assume '{' was consumed. 165 | fn parse_object(lexer: &mut Lexer<'_, Token>) -> Result { 166 | let mut map = HashMap::new(); 167 | let span = lexer.span(); 168 | let mut awaits_comma = false; 169 | let mut awaits_key = false; 170 | 171 | while let Some(token) = lexer.next() { 172 | match token { 173 | Ok(Token::BraceClose) if !awaits_key => return Ok(Value::Object(map)), 174 | Ok(Token::Comma) if awaits_comma => awaits_key = true, 175 | Ok(Token::String(key)) if !awaits_comma => { 176 | match lexer.next() { 177 | Some(Ok(Token::Colon)) => (), 178 | _ => { 179 | return Err(( 180 | "unexpected token here, expecting ':'".to_owned(), 181 | lexer.span(), 182 | )) 183 | } 184 | } 185 | let value = parse_value(lexer)?; 186 | map.insert(key, value); 187 | awaits_key = false; 188 | } 189 | _ => { 190 | return Err(( 191 | "unexpected token here (context: object)".to_owned(), 192 | lexer.span(), 193 | )) 194 | } 195 | } 196 | awaits_comma = !awaits_key; 197 | } 198 | Err(("unmatched opening brace defined here".to_owned(), span)) 199 | } 200 | /* ANCHOR_END: object */ 201 | -------------------------------------------------------------------------------- /examples/nom-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nom-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "nom-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | nom = "8.0.0" 11 | nom-language = "0.1.0" 12 | 13 | [lints] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/nom-app/app.rs: -------------------------------------------------------------------------------- 1 | mod parser; 2 | 3 | use std::{env, fs}; 4 | 5 | use nom::Err; 6 | use nom_language::error::convert_error; 7 | use nom_language::error::VerboseError; 8 | 9 | fn main() { 10 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 11 | .expect("Failed to read file"); 12 | 13 | match parser::root::>(src.as_str()) { 14 | Ok(json) => { 15 | #[cfg(debug_assertions)] 16 | { 17 | println!("{:#?}", json); 18 | } 19 | #[cfg(not(debug_assertions))] 20 | { 21 | std::hint::black_box(json); 22 | } 23 | } 24 | Err(Err::Error(err)) | Err(Err::Failure(err)) => { 25 | let err = convert_error(src.as_str(), err); 26 | eprintln!("{}", err); 27 | std::process::exit(1); 28 | } 29 | Err(err) => { 30 | eprintln!("{}", err); 31 | std::process::exit(1); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/nom-app/parser.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | branch::alt, 3 | bytes::complete::{escaped, tag, take_while}, 4 | character::complete::{alphanumeric1 as alphanumeric, char, one_of}, 5 | combinator::{cut, map, opt, value}, 6 | error::{context, ContextError, ParseError}, 7 | multi::separated_list0, 8 | number::complete::double, 9 | sequence::{delimited, preceded, separated_pair, terminated}, 10 | IResult, Parser, 11 | }; 12 | use std::collections::HashMap; 13 | use std::str; 14 | 15 | #[derive(Debug, PartialEq)] 16 | pub enum JsonValue { 17 | Null, 18 | Str(String), 19 | Boolean(bool), 20 | Num(f64), 21 | Array(Vec), 22 | Object(HashMap), 23 | } 24 | 25 | fn sp<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> { 26 | let chars = " \t\r\n"; 27 | 28 | take_while(move |c| chars.contains(c))(i) 29 | } 30 | 31 | fn parse_str<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> { 32 | escaped(alphanumeric, '\\', one_of("\"n\\"))(i) 33 | } 34 | 35 | fn boolean<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, bool, E> { 36 | let parse_true = value(true, tag("true")); 37 | 38 | let parse_false = value(false, tag("false")); 39 | 40 | alt((parse_true, parse_false)).parse(input) 41 | } 42 | 43 | fn null<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, (), E> { 44 | value((), tag("null")).parse(input) 45 | } 46 | 47 | fn string<'a, E: ParseError<&'a str> + ContextError<&'a str>>( 48 | i: &'a str, 49 | ) -> IResult<&'a str, &'a str, E> { 50 | context( 51 | "string", 52 | preceded(char('\"'), cut(terminated(parse_str, char('\"')))), 53 | ) 54 | .parse(i) 55 | } 56 | 57 | fn array<'a, E: ParseError<&'a str> + ContextError<&'a str>>( 58 | i: &'a str, 59 | ) -> IResult<&'a str, Vec, E> { 60 | context( 61 | "array", 62 | preceded( 63 | char('['), 64 | cut(terminated( 65 | separated_list0(preceded(sp, char(',')), json_value), 66 | preceded(sp, char(']')), 67 | )), 68 | ), 69 | ) 70 | .parse(i) 71 | } 72 | 73 | fn key_value<'a, E: ParseError<&'a str> + ContextError<&'a str>>( 74 | i: &'a str, 75 | ) -> IResult<&'a str, (&'a str, JsonValue), E> { 76 | separated_pair( 77 | preceded(sp, string), 78 | cut(preceded(sp, char(':'))), 79 | json_value, 80 | ) 81 | .parse(i) 82 | } 83 | 84 | fn hash<'a, E: ParseError<&'a str> + ContextError<&'a str>>( 85 | i: &'a str, 86 | ) -> IResult<&'a str, HashMap, E> { 87 | context( 88 | "map", 89 | preceded( 90 | char('{'), 91 | cut(terminated( 92 | map( 93 | separated_list0(preceded(sp, char(',')), key_value), 94 | |tuple_vec| { 95 | tuple_vec 96 | .into_iter() 97 | .map(|(k, v)| (String::from(k), v)) 98 | .collect() 99 | }, 100 | ), 101 | preceded(sp, char('}')), 102 | )), 103 | ), 104 | ) 105 | .parse(i) 106 | } 107 | 108 | fn json_value<'a, E: ParseError<&'a str> + ContextError<&'a str>>( 109 | i: &'a str, 110 | ) -> IResult<&'a str, JsonValue, E> { 111 | preceded( 112 | sp, 113 | alt(( 114 | map(hash, JsonValue::Object), 115 | map(array, JsonValue::Array), 116 | map(string, |s| JsonValue::Str(String::from(s))), 117 | map(double, JsonValue::Num), 118 | map(boolean, JsonValue::Boolean), 119 | map(null, |_| JsonValue::Null), 120 | )), 121 | ) 122 | .parse(i) 123 | } 124 | 125 | pub fn root<'a, E: ParseError<&'a str> + ContextError<&'a str>>( 126 | i: &'a str, 127 | ) -> IResult<&'a str, JsonValue, E> { 128 | delimited( 129 | sp, 130 | alt(( 131 | map(hash, JsonValue::Object), 132 | map(array, JsonValue::Array), 133 | map(null, |_| JsonValue::Null), 134 | )), 135 | opt(sp), 136 | ) 137 | .parse(i) 138 | } 139 | -------------------------------------------------------------------------------- /examples/null-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "null-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "null-app" 7 | path = "app.rs" 8 | 9 | [lints] 10 | workspace = true 11 | -------------------------------------------------------------------------------- /examples/null-app/app.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 5 | .expect("Failed to read file"); 6 | 7 | #[cfg(debug_assertions)] 8 | { 9 | println!("{:#?}", src); 10 | } 11 | #[cfg(not(debug_assertions))] 12 | { 13 | std::hint::black_box(src); 14 | } 15 | 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /examples/parol-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parol-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "parol-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | parol_runtime = "3.0.0" 11 | 12 | [dev-dependencies] 13 | parol = "3.0.1" 14 | snapbox = "0.6.21" 15 | 16 | [lints] 17 | workspace = true 18 | -------------------------------------------------------------------------------- /examples/parol-app/app.rs: -------------------------------------------------------------------------------- 1 | mod grammar; 2 | mod grammar_trait; 3 | mod parser; 4 | 5 | use std::{env, fs}; 6 | 7 | use parol_runtime::Report; 8 | 9 | struct JSONErrorReporter; 10 | impl Report for JSONErrorReporter {} 11 | 12 | fn main() { 13 | let path = env::args().nth(1).expect("Expected file argument"); 14 | let src = fs::read_to_string(&path).expect("Failed to read file"); 15 | 16 | let mut json_grammar = grammar::Grammar::new(); 17 | match parser::parse(&src, &path, &mut json_grammar) { 18 | Ok(_) => { 19 | #[cfg(debug_assertions)] 20 | { 21 | println!("{}", json_grammar); 22 | } 23 | } 24 | Err(err) => { 25 | let _ = JSONErrorReporter::report_error(&err, &path); 26 | std::process::exit(1); 27 | } 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /examples/parol-app/grammar.rs: -------------------------------------------------------------------------------- 1 | use crate::grammar_trait::*; 2 | use parol_runtime::Result; 3 | use std::fmt::{Debug, Display, Error, Formatter}; 4 | 5 | impl Display for Json<'_> { 6 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { 7 | write!(f, "{}", self.value) 8 | } 9 | } 10 | 11 | impl Display for Value<'_> { 12 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { 13 | match self { 14 | Value::String(v) => write!(f, "{}", v.string.string.text()), 15 | Value::Number(v) => write!(f, "{}", v.number.number.text()), 16 | Value::Object(v) => write!(f, "{{{}}}", v.object.object_suffix), 17 | Value::Array(v) => write!(f, "[{}]", v.array.array_suffix), 18 | Value::True(_) => write!(f, "true"), 19 | Value::False(_) => write!(f, "false"), 20 | Value::Null(_) => write!(f, "null"), 21 | } 22 | } 23 | } 24 | 25 | impl Display for ObjectSuffix<'_> { 26 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { 27 | match self { 28 | ObjectSuffix::PairObjectListRBrace(o) => write!( 29 | f, 30 | "{}{}", 31 | o.pair, 32 | o.object_list 33 | .iter() 34 | .map(|e| format!("{}", e)) 35 | .collect::>() 36 | .join("") 37 | ), 38 | ObjectSuffix::RBrace(_) => Ok(()), 39 | } 40 | } 41 | } 42 | 43 | impl Display for ObjectList<'_> { 44 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { 45 | write!(f, ", {}", self.pair) 46 | } 47 | } 48 | 49 | impl Display for ArraySuffix<'_> { 50 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { 51 | match self { 52 | ArraySuffix::ValueArrayListRBracket(a) => write!( 53 | f, 54 | "{}{}", 55 | a.value, 56 | a.array_list 57 | .iter() 58 | .map(|e| format!("{}", e)) 59 | .collect::>() 60 | .join("") 61 | ), 62 | ArraySuffix::RBracket(_) => Ok(()), 63 | } 64 | } 65 | } 66 | 67 | impl Display for ArrayList<'_> { 68 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { 69 | write!(f, ", {}", self.value) 70 | } 71 | } 72 | 73 | impl Display for Pair<'_> { 74 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { 75 | write!(f, "{}: {}", self.string.string.text(), self.value) 76 | } 77 | } 78 | 79 | /// 80 | /// Data structure used to build up a json structure during parsing 81 | /// 82 | #[derive(Debug, Default)] 83 | pub struct Grammar<'t> { 84 | pub json: Option>, 85 | } 86 | 87 | impl Display for Grammar<'_> { 88 | fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { 89 | match &self.json { 90 | Some(json) => write!(f, "{}", json), 91 | None => write!(f, "No parse result"), 92 | } 93 | } 94 | } 95 | 96 | impl Grammar<'_> { 97 | pub fn new() -> Self { 98 | Grammar::default() 99 | } 100 | } 101 | 102 | impl<'t> GrammarTrait<'t> for Grammar<'t> { 103 | fn json(&mut self, arg: &Json<'t>) -> Result<()> { 104 | self.json = Some(arg.clone()); 105 | Ok(()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/parol-app/grammar_trait.rs: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------- 2 | // This file was generated by parol. 3 | // It is not intended for manual editing and changes will be 4 | // lost after next build. 5 | // --------------------------------------------------------- 6 | 7 | // Disable clippy warnings that can result in the way how parol generates code. 8 | #![allow(clippy::enum_variant_names)] 9 | #![allow(clippy::large_enum_variant)] 10 | #![allow(clippy::upper_case_acronyms)] 11 | 12 | use parol_runtime::derive_builder::Builder; 13 | use parol_runtime::log::trace; 14 | #[allow(unused_imports)] 15 | use parol_runtime::parol_macros::{pop_and_reverse_item, pop_item}; 16 | use parol_runtime::parser::{ParseTreeType, UserActionsTrait}; 17 | use parol_runtime::{ParserError, Result, Token}; 18 | 19 | /// Semantic actions trait generated for the user grammar 20 | /// All functions have default implementations. 21 | pub trait GrammarTrait<'t> { 22 | /// Semantic action for non-terminal 'Json' 23 | fn json(&mut self, _arg: &Json<'t>) -> Result<()> { 24 | Ok(()) 25 | } 26 | 27 | /// Semantic action for non-terminal 'Object' 28 | fn object(&mut self, _arg: &Object<'t>) -> Result<()> { 29 | Ok(()) 30 | } 31 | 32 | /// Semantic action for non-terminal 'Pair' 33 | fn pair(&mut self, _arg: &Pair<'t>) -> Result<()> { 34 | Ok(()) 35 | } 36 | 37 | /// Semantic action for non-terminal 'Array' 38 | fn array(&mut self, _arg: &Array<'t>) -> Result<()> { 39 | Ok(()) 40 | } 41 | 42 | /// Semantic action for non-terminal 'Value' 43 | fn value(&mut self, _arg: &Value<'t>) -> Result<()> { 44 | Ok(()) 45 | } 46 | 47 | /// Semantic action for non-terminal 'String' 48 | fn string(&mut self, _arg: &String<'t>) -> Result<()> { 49 | Ok(()) 50 | } 51 | 52 | /// Semantic action for non-terminal 'Number' 53 | fn number(&mut self, _arg: &Number<'t>) -> Result<()> { 54 | Ok(()) 55 | } 56 | 57 | /// This method provides skipped language comments. 58 | /// If you need comments please provide your own implementation of this method. 59 | fn on_comment(&mut self, _token: Token<'t>) {} 60 | } 61 | 62 | // ------------------------------------------------------------------------------------------------- 63 | // 64 | // Output Types of productions deduced from the structure of the transformed grammar 65 | // 66 | 67 | /// 68 | /// Type derived for production 2 69 | /// 70 | /// `ObjectSuffix: Pair ObjectList /* Vec */ '}'^ /* Clipped */;` 71 | /// 72 | #[allow(dead_code)] 73 | #[derive(Builder, Debug, Clone)] 74 | #[builder(crate = "parol_runtime::derive_builder")] 75 | pub struct ObjectSuffixPairObjectListRBrace<'t> { 76 | pub pair: Box>, 77 | pub object_list: Vec>, 78 | } 79 | 80 | /// 81 | /// Type derived for production 3 82 | /// 83 | /// `ObjectSuffix: '}'^ /* Clipped */;` 84 | /// 85 | #[allow(dead_code)] 86 | #[derive(Builder, Debug, Clone)] 87 | #[builder(crate = "parol_runtime::derive_builder")] 88 | pub struct ObjectSuffixRBrace {} 89 | 90 | /// 91 | /// Type derived for production 8 92 | /// 93 | /// `ArraySuffix: Value ArrayList /* Vec */ ']'^ /* Clipped */;` 94 | /// 95 | #[allow(dead_code)] 96 | #[derive(Builder, Debug, Clone)] 97 | #[builder(crate = "parol_runtime::derive_builder")] 98 | pub struct ArraySuffixValueArrayListRBracket<'t> { 99 | pub value: Box>, 100 | pub array_list: Vec>, 101 | } 102 | 103 | /// 104 | /// Type derived for production 9 105 | /// 106 | /// `ArraySuffix: ']'^ /* Clipped */;` 107 | /// 108 | #[allow(dead_code)] 109 | #[derive(Builder, Debug, Clone)] 110 | #[builder(crate = "parol_runtime::derive_builder")] 111 | pub struct ArraySuffixRBracket {} 112 | 113 | /// 114 | /// Type derived for production 12 115 | /// 116 | /// `Value: String;` 117 | /// 118 | #[allow(dead_code)] 119 | #[derive(Builder, Debug, Clone)] 120 | #[builder(crate = "parol_runtime::derive_builder")] 121 | pub struct ValueString<'t> { 122 | pub string: Box>, 123 | } 124 | 125 | /// 126 | /// Type derived for production 13 127 | /// 128 | /// `Value: Number;` 129 | /// 130 | #[allow(dead_code)] 131 | #[derive(Builder, Debug, Clone)] 132 | #[builder(crate = "parol_runtime::derive_builder")] 133 | pub struct ValueNumber<'t> { 134 | pub number: Box>, 135 | } 136 | 137 | /// 138 | /// Type derived for production 14 139 | /// 140 | /// `Value: Object;` 141 | /// 142 | #[allow(dead_code)] 143 | #[derive(Builder, Debug, Clone)] 144 | #[builder(crate = "parol_runtime::derive_builder")] 145 | pub struct ValueObject<'t> { 146 | pub object: Box>, 147 | } 148 | 149 | /// 150 | /// Type derived for production 15 151 | /// 152 | /// `Value: Array;` 153 | /// 154 | #[allow(dead_code)] 155 | #[derive(Builder, Debug, Clone)] 156 | #[builder(crate = "parol_runtime::derive_builder")] 157 | pub struct ValueArray<'t> { 158 | pub array: Box>, 159 | } 160 | 161 | /// 162 | /// Type derived for production 16 163 | /// 164 | /// `Value: 'true'^ /* Clipped */;` 165 | /// 166 | #[allow(dead_code)] 167 | #[derive(Builder, Debug, Clone)] 168 | #[builder(crate = "parol_runtime::derive_builder")] 169 | pub struct ValueTrue {} 170 | 171 | /// 172 | /// Type derived for production 17 173 | /// 174 | /// `Value: 'false'^ /* Clipped */;` 175 | /// 176 | #[allow(dead_code)] 177 | #[derive(Builder, Debug, Clone)] 178 | #[builder(crate = "parol_runtime::derive_builder")] 179 | pub struct ValueFalse {} 180 | 181 | /// 182 | /// Type derived for production 18 183 | /// 184 | /// `Value: 'null'^ /* Clipped */;` 185 | /// 186 | #[allow(dead_code)] 187 | #[derive(Builder, Debug, Clone)] 188 | #[builder(crate = "parol_runtime::derive_builder")] 189 | pub struct ValueNull {} 190 | 191 | // ------------------------------------------------------------------------------------------------- 192 | // 193 | // Types of non-terminals deduced from the structure of the transformed grammar 194 | // 195 | 196 | /// 197 | /// Type derived for non-terminal Array 198 | /// 199 | #[allow(dead_code)] 200 | #[derive(Builder, Debug, Clone)] 201 | #[builder(crate = "parol_runtime::derive_builder")] 202 | pub struct Array<'t> { 203 | pub array_suffix: Box>, 204 | } 205 | 206 | /// 207 | /// Type derived for non-terminal ArrayList 208 | /// 209 | #[allow(dead_code)] 210 | #[derive(Builder, Debug, Clone)] 211 | #[builder(crate = "parol_runtime::derive_builder")] 212 | pub struct ArrayList<'t> { 213 | pub value: Box>, 214 | } 215 | 216 | /// 217 | /// Type derived for non-terminal ArraySuffix 218 | /// 219 | #[allow(dead_code)] 220 | #[derive(Debug, Clone)] 221 | pub enum ArraySuffix<'t> { 222 | ValueArrayListRBracket(ArraySuffixValueArrayListRBracket<'t>), 223 | RBracket(ArraySuffixRBracket), 224 | } 225 | 226 | /// 227 | /// Type derived for non-terminal Json 228 | /// 229 | #[allow(dead_code)] 230 | #[derive(Builder, Debug, Clone)] 231 | #[builder(crate = "parol_runtime::derive_builder")] 232 | pub struct Json<'t> { 233 | pub value: Box>, 234 | } 235 | 236 | /// 237 | /// Type derived for non-terminal Number 238 | /// 239 | #[allow(dead_code)] 240 | #[derive(Builder, Debug, Clone)] 241 | #[builder(crate = "parol_runtime::derive_builder")] 242 | pub struct Number<'t> { 243 | pub number: Token<'t>, /* -?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][-+]?(0|[1-9][0-9]*)?)? */ 244 | } 245 | 246 | /// 247 | /// Type derived for non-terminal Object 248 | /// 249 | #[allow(dead_code)] 250 | #[derive(Builder, Debug, Clone)] 251 | #[builder(crate = "parol_runtime::derive_builder")] 252 | pub struct Object<'t> { 253 | pub object_suffix: Box>, 254 | } 255 | 256 | /// 257 | /// Type derived for non-terminal ObjectList 258 | /// 259 | #[allow(dead_code)] 260 | #[derive(Builder, Debug, Clone)] 261 | #[builder(crate = "parol_runtime::derive_builder")] 262 | pub struct ObjectList<'t> { 263 | pub pair: Box>, 264 | } 265 | 266 | /// 267 | /// Type derived for non-terminal ObjectSuffix 268 | /// 269 | #[allow(dead_code)] 270 | #[derive(Debug, Clone)] 271 | pub enum ObjectSuffix<'t> { 272 | PairObjectListRBrace(ObjectSuffixPairObjectListRBrace<'t>), 273 | RBrace(ObjectSuffixRBrace), 274 | } 275 | 276 | /// 277 | /// Type derived for non-terminal Pair 278 | /// 279 | #[allow(dead_code)] 280 | #[derive(Builder, Debug, Clone)] 281 | #[builder(crate = "parol_runtime::derive_builder")] 282 | pub struct Pair<'t> { 283 | pub string: Box>, 284 | pub value: Box>, 285 | } 286 | 287 | /// 288 | /// Type derived for non-terminal String 289 | /// 290 | #[allow(dead_code)] 291 | #[derive(Builder, Debug, Clone)] 292 | #[builder(crate = "parol_runtime::derive_builder")] 293 | pub struct String<'t> { 294 | pub string: Token<'t>, /* "(\\.|[^"])*" */ 295 | } 296 | 297 | /// 298 | /// Type derived for non-terminal Value 299 | /// 300 | #[allow(dead_code)] 301 | #[derive(Debug, Clone)] 302 | pub enum Value<'t> { 303 | String(ValueString<'t>), 304 | Number(ValueNumber<'t>), 305 | Object(ValueObject<'t>), 306 | Array(ValueArray<'t>), 307 | True(ValueTrue), 308 | False(ValueFalse), 309 | Null(ValueNull), 310 | } 311 | 312 | // ------------------------------------------------------------------------------------------------- 313 | 314 | /// 315 | /// Deduced ASTType of expanded grammar 316 | /// 317 | #[allow(dead_code)] 318 | #[derive(Debug, Clone)] 319 | pub enum ASTType<'t> { 320 | Array(Array<'t>), 321 | ArrayList(Vec>), 322 | ArraySuffix(ArraySuffix<'t>), 323 | Json(Json<'t>), 324 | Number(Number<'t>), 325 | Object(Object<'t>), 326 | ObjectList(Vec>), 327 | ObjectSuffix(ObjectSuffix<'t>), 328 | Pair(Pair<'t>), 329 | String(String<'t>), 330 | Value(Value<'t>), 331 | } 332 | 333 | /// Auto-implemented adapter grammar 334 | /// 335 | /// The lifetime parameter `'t` refers to the lifetime of the scanned text. 336 | /// The lifetime parameter `'u` refers to the lifetime of user grammar object. 337 | /// 338 | #[allow(dead_code)] 339 | pub struct GrammarAuto<'t, 'u> 340 | where 341 | 't: 'u, 342 | { 343 | // Mutable reference of the actual user grammar to be able to call the semantic actions on it 344 | user_grammar: &'u mut dyn GrammarTrait<'t>, 345 | // Stack to construct the AST on it 346 | item_stack: Vec>, 347 | } 348 | 349 | /// 350 | /// The `GrammarAuto` impl is automatically generated for the 351 | /// given grammar. 352 | /// 353 | impl<'t, 'u> GrammarAuto<'t, 'u> { 354 | pub fn new(user_grammar: &'u mut dyn GrammarTrait<'t>) -> Self { 355 | Self { 356 | user_grammar, 357 | item_stack: Vec::new(), 358 | } 359 | } 360 | 361 | #[allow(dead_code)] 362 | fn push(&mut self, item: ASTType<'t>, context: &str) { 363 | trace!("push {}: {:?}", context, item); 364 | self.item_stack.push(item) 365 | } 366 | 367 | #[allow(dead_code)] 368 | fn pop(&mut self, context: &str) -> Option> { 369 | let item = self.item_stack.pop(); 370 | if let Some(ref item) = item { 371 | trace!("pop {}: {:?}", context, item); 372 | } 373 | item 374 | } 375 | 376 | #[allow(dead_code)] 377 | // Use this function for debugging purposes: 378 | // trace!("{}", self.trace_item_stack(context)); 379 | fn trace_item_stack(&self, context: &str) -> std::string::String { 380 | format!( 381 | "Item stack at {}:\n{}", 382 | context, 383 | self.item_stack 384 | .iter() 385 | .rev() 386 | .map(|s| format!(" {:?}", s)) 387 | .collect::>() 388 | .join("\n") 389 | ) 390 | } 391 | 392 | /// Semantic action for production 0: 393 | /// 394 | /// `Json: Value;` 395 | /// 396 | #[parol_runtime::function_name::named] 397 | fn json(&mut self, _value: &ParseTreeType<'t>) -> Result<()> { 398 | let context = function_name!(); 399 | trace!("{}", self.trace_item_stack(context)); 400 | let value = pop_item!(self, value, Value, context); 401 | let json_built = Json { 402 | value: Box::new(value), 403 | }; 404 | // Calling user action here 405 | self.user_grammar.json(&json_built)?; 406 | self.push(ASTType::Json(json_built), context); 407 | Ok(()) 408 | } 409 | 410 | /// Semantic action for production 1: 411 | /// 412 | /// `Object: '{'^ /* Clipped */ ObjectSuffix;` 413 | /// 414 | #[parol_runtime::function_name::named] 415 | fn object( 416 | &mut self, 417 | _l_brace: &ParseTreeType<'t>, 418 | _object_suffix: &ParseTreeType<'t>, 419 | ) -> Result<()> { 420 | let context = function_name!(); 421 | trace!("{}", self.trace_item_stack(context)); 422 | let object_suffix = pop_item!(self, object_suffix, ObjectSuffix, context); 423 | let object_built = Object { 424 | object_suffix: Box::new(object_suffix), 425 | }; 426 | // Calling user action here 427 | self.user_grammar.object(&object_built)?; 428 | self.push(ASTType::Object(object_built), context); 429 | Ok(()) 430 | } 431 | 432 | /// Semantic action for production 2: 433 | /// 434 | /// `ObjectSuffix: Pair ObjectList /* Vec */ '}'^ /* Clipped */;` 435 | /// 436 | #[parol_runtime::function_name::named] 437 | fn object_suffix_0( 438 | &mut self, 439 | _pair: &ParseTreeType<'t>, 440 | _object_list: &ParseTreeType<'t>, 441 | _r_brace: &ParseTreeType<'t>, 442 | ) -> Result<()> { 443 | let context = function_name!(); 444 | trace!("{}", self.trace_item_stack(context)); 445 | let object_list = pop_and_reverse_item!(self, object_list, ObjectList, context); 446 | let pair = pop_item!(self, pair, Pair, context); 447 | let object_suffix_0_built = ObjectSuffixPairObjectListRBrace { 448 | pair: Box::new(pair), 449 | object_list, 450 | }; 451 | let object_suffix_0_built = ObjectSuffix::PairObjectListRBrace(object_suffix_0_built); 452 | self.push(ASTType::ObjectSuffix(object_suffix_0_built), context); 453 | Ok(()) 454 | } 455 | 456 | /// Semantic action for production 3: 457 | /// 458 | /// `ObjectSuffix: '}'^ /* Clipped */;` 459 | /// 460 | #[parol_runtime::function_name::named] 461 | fn object_suffix_1(&mut self, _r_brace: &ParseTreeType<'t>) -> Result<()> { 462 | let context = function_name!(); 463 | trace!("{}", self.trace_item_stack(context)); 464 | let object_suffix_1_built = ObjectSuffixRBrace {}; 465 | let object_suffix_1_built = ObjectSuffix::RBrace(object_suffix_1_built); 466 | self.push(ASTType::ObjectSuffix(object_suffix_1_built), context); 467 | Ok(()) 468 | } 469 | 470 | /// Semantic action for production 4: 471 | /// 472 | /// `ObjectList /* Vec::Push */: ','^ /* Clipped */ Pair ObjectList;` 473 | /// 474 | #[parol_runtime::function_name::named] 475 | fn object_list_0( 476 | &mut self, 477 | _comma: &ParseTreeType<'t>, 478 | _pair: &ParseTreeType<'t>, 479 | _object_list: &ParseTreeType<'t>, 480 | ) -> Result<()> { 481 | let context = function_name!(); 482 | trace!("{}", self.trace_item_stack(context)); 483 | let mut object_list = pop_item!(self, object_list, ObjectList, context); 484 | let pair = pop_item!(self, pair, Pair, context); 485 | let object_list_0_built = ObjectList { 486 | pair: Box::new(pair), 487 | }; 488 | // Add an element to the vector 489 | object_list.push(object_list_0_built); 490 | self.push(ASTType::ObjectList(object_list), context); 491 | Ok(()) 492 | } 493 | 494 | /// Semantic action for production 5: 495 | /// 496 | /// `ObjectList /* Vec::New */: ;` 497 | /// 498 | #[parol_runtime::function_name::named] 499 | fn object_list_1(&mut self) -> Result<()> { 500 | let context = function_name!(); 501 | trace!("{}", self.trace_item_stack(context)); 502 | let object_list_1_built = Vec::new(); 503 | self.push(ASTType::ObjectList(object_list_1_built), context); 504 | Ok(()) 505 | } 506 | 507 | /// Semantic action for production 6: 508 | /// 509 | /// `Pair: String ':'^ /* Clipped */ Value;` 510 | /// 511 | #[parol_runtime::function_name::named] 512 | fn pair( 513 | &mut self, 514 | _string: &ParseTreeType<'t>, 515 | _colon: &ParseTreeType<'t>, 516 | _value: &ParseTreeType<'t>, 517 | ) -> Result<()> { 518 | let context = function_name!(); 519 | trace!("{}", self.trace_item_stack(context)); 520 | let value = pop_item!(self, value, Value, context); 521 | let string = pop_item!(self, string, String, context); 522 | let pair_built = Pair { 523 | string: Box::new(string), 524 | value: Box::new(value), 525 | }; 526 | // Calling user action here 527 | self.user_grammar.pair(&pair_built)?; 528 | self.push(ASTType::Pair(pair_built), context); 529 | Ok(()) 530 | } 531 | 532 | /// Semantic action for production 7: 533 | /// 534 | /// `Array: '['^ /* Clipped */ ArraySuffix;` 535 | /// 536 | #[parol_runtime::function_name::named] 537 | fn array( 538 | &mut self, 539 | _l_bracket: &ParseTreeType<'t>, 540 | _array_suffix: &ParseTreeType<'t>, 541 | ) -> Result<()> { 542 | let context = function_name!(); 543 | trace!("{}", self.trace_item_stack(context)); 544 | let array_suffix = pop_item!(self, array_suffix, ArraySuffix, context); 545 | let array_built = Array { 546 | array_suffix: Box::new(array_suffix), 547 | }; 548 | // Calling user action here 549 | self.user_grammar.array(&array_built)?; 550 | self.push(ASTType::Array(array_built), context); 551 | Ok(()) 552 | } 553 | 554 | /// Semantic action for production 8: 555 | /// 556 | /// `ArraySuffix: Value ArrayList /* Vec */ ']'^ /* Clipped */;` 557 | /// 558 | #[parol_runtime::function_name::named] 559 | fn array_suffix_0( 560 | &mut self, 561 | _value: &ParseTreeType<'t>, 562 | _array_list: &ParseTreeType<'t>, 563 | _r_bracket: &ParseTreeType<'t>, 564 | ) -> Result<()> { 565 | let context = function_name!(); 566 | trace!("{}", self.trace_item_stack(context)); 567 | let array_list = pop_and_reverse_item!(self, array_list, ArrayList, context); 568 | let value = pop_item!(self, value, Value, context); 569 | let array_suffix_0_built = ArraySuffixValueArrayListRBracket { 570 | value: Box::new(value), 571 | array_list, 572 | }; 573 | let array_suffix_0_built = ArraySuffix::ValueArrayListRBracket(array_suffix_0_built); 574 | self.push(ASTType::ArraySuffix(array_suffix_0_built), context); 575 | Ok(()) 576 | } 577 | 578 | /// Semantic action for production 9: 579 | /// 580 | /// `ArraySuffix: ']'^ /* Clipped */;` 581 | /// 582 | #[parol_runtime::function_name::named] 583 | fn array_suffix_1(&mut self, _r_bracket: &ParseTreeType<'t>) -> Result<()> { 584 | let context = function_name!(); 585 | trace!("{}", self.trace_item_stack(context)); 586 | let array_suffix_1_built = ArraySuffixRBracket {}; 587 | let array_suffix_1_built = ArraySuffix::RBracket(array_suffix_1_built); 588 | self.push(ASTType::ArraySuffix(array_suffix_1_built), context); 589 | Ok(()) 590 | } 591 | 592 | /// Semantic action for production 10: 593 | /// 594 | /// `ArrayList /* Vec::Push */: ','^ /* Clipped */ Value ArrayList;` 595 | /// 596 | #[parol_runtime::function_name::named] 597 | fn array_list_0( 598 | &mut self, 599 | _comma: &ParseTreeType<'t>, 600 | _value: &ParseTreeType<'t>, 601 | _array_list: &ParseTreeType<'t>, 602 | ) -> Result<()> { 603 | let context = function_name!(); 604 | trace!("{}", self.trace_item_stack(context)); 605 | let mut array_list = pop_item!(self, array_list, ArrayList, context); 606 | let value = pop_item!(self, value, Value, context); 607 | let array_list_0_built = ArrayList { 608 | value: Box::new(value), 609 | }; 610 | // Add an element to the vector 611 | array_list.push(array_list_0_built); 612 | self.push(ASTType::ArrayList(array_list), context); 613 | Ok(()) 614 | } 615 | 616 | /// Semantic action for production 11: 617 | /// 618 | /// `ArrayList /* Vec::New */: ;` 619 | /// 620 | #[parol_runtime::function_name::named] 621 | fn array_list_1(&mut self) -> Result<()> { 622 | let context = function_name!(); 623 | trace!("{}", self.trace_item_stack(context)); 624 | let array_list_1_built = Vec::new(); 625 | self.push(ASTType::ArrayList(array_list_1_built), context); 626 | Ok(()) 627 | } 628 | 629 | /// Semantic action for production 12: 630 | /// 631 | /// `Value: String;` 632 | /// 633 | #[parol_runtime::function_name::named] 634 | fn value_0(&mut self, _string: &ParseTreeType<'t>) -> Result<()> { 635 | let context = function_name!(); 636 | trace!("{}", self.trace_item_stack(context)); 637 | let string = pop_item!(self, string, String, context); 638 | let value_0_built = ValueString { 639 | string: Box::new(string), 640 | }; 641 | let value_0_built = Value::String(value_0_built); 642 | // Calling user action here 643 | self.user_grammar.value(&value_0_built)?; 644 | self.push(ASTType::Value(value_0_built), context); 645 | Ok(()) 646 | } 647 | 648 | /// Semantic action for production 13: 649 | /// 650 | /// `Value: Number;` 651 | /// 652 | #[parol_runtime::function_name::named] 653 | fn value_1(&mut self, _number: &ParseTreeType<'t>) -> Result<()> { 654 | let context = function_name!(); 655 | trace!("{}", self.trace_item_stack(context)); 656 | let number = pop_item!(self, number, Number, context); 657 | let value_1_built = ValueNumber { 658 | number: Box::new(number), 659 | }; 660 | let value_1_built = Value::Number(value_1_built); 661 | // Calling user action here 662 | self.user_grammar.value(&value_1_built)?; 663 | self.push(ASTType::Value(value_1_built), context); 664 | Ok(()) 665 | } 666 | 667 | /// Semantic action for production 14: 668 | /// 669 | /// `Value: Object;` 670 | /// 671 | #[parol_runtime::function_name::named] 672 | fn value_2(&mut self, _object: &ParseTreeType<'t>) -> Result<()> { 673 | let context = function_name!(); 674 | trace!("{}", self.trace_item_stack(context)); 675 | let object = pop_item!(self, object, Object, context); 676 | let value_2_built = ValueObject { 677 | object: Box::new(object), 678 | }; 679 | let value_2_built = Value::Object(value_2_built); 680 | // Calling user action here 681 | self.user_grammar.value(&value_2_built)?; 682 | self.push(ASTType::Value(value_2_built), context); 683 | Ok(()) 684 | } 685 | 686 | /// Semantic action for production 15: 687 | /// 688 | /// `Value: Array;` 689 | /// 690 | #[parol_runtime::function_name::named] 691 | fn value_3(&mut self, _array: &ParseTreeType<'t>) -> Result<()> { 692 | let context = function_name!(); 693 | trace!("{}", self.trace_item_stack(context)); 694 | let array = pop_item!(self, array, Array, context); 695 | let value_3_built = ValueArray { 696 | array: Box::new(array), 697 | }; 698 | let value_3_built = Value::Array(value_3_built); 699 | // Calling user action here 700 | self.user_grammar.value(&value_3_built)?; 701 | self.push(ASTType::Value(value_3_built), context); 702 | Ok(()) 703 | } 704 | 705 | /// Semantic action for production 16: 706 | /// 707 | /// `Value: 'true'^ /* Clipped */;` 708 | /// 709 | #[parol_runtime::function_name::named] 710 | fn value_4(&mut self, _true: &ParseTreeType<'t>) -> Result<()> { 711 | let context = function_name!(); 712 | trace!("{}", self.trace_item_stack(context)); 713 | let value_4_built = ValueTrue {}; 714 | let value_4_built = Value::True(value_4_built); 715 | // Calling user action here 716 | self.user_grammar.value(&value_4_built)?; 717 | self.push(ASTType::Value(value_4_built), context); 718 | Ok(()) 719 | } 720 | 721 | /// Semantic action for production 17: 722 | /// 723 | /// `Value: 'false'^ /* Clipped */;` 724 | /// 725 | #[parol_runtime::function_name::named] 726 | fn value_5(&mut self, _false: &ParseTreeType<'t>) -> Result<()> { 727 | let context = function_name!(); 728 | trace!("{}", self.trace_item_stack(context)); 729 | let value_5_built = ValueFalse {}; 730 | let value_5_built = Value::False(value_5_built); 731 | // Calling user action here 732 | self.user_grammar.value(&value_5_built)?; 733 | self.push(ASTType::Value(value_5_built), context); 734 | Ok(()) 735 | } 736 | 737 | /// Semantic action for production 18: 738 | /// 739 | /// `Value: 'null'^ /* Clipped */;` 740 | /// 741 | #[parol_runtime::function_name::named] 742 | fn value_6(&mut self, _null: &ParseTreeType<'t>) -> Result<()> { 743 | let context = function_name!(); 744 | trace!("{}", self.trace_item_stack(context)); 745 | let value_6_built = ValueNull {}; 746 | let value_6_built = Value::Null(value_6_built); 747 | // Calling user action here 748 | self.user_grammar.value(&value_6_built)?; 749 | self.push(ASTType::Value(value_6_built), context); 750 | Ok(()) 751 | } 752 | 753 | /// Semantic action for production 19: 754 | /// 755 | /// `String: /"(\\.|[^"])*"/;` 756 | /// 757 | #[parol_runtime::function_name::named] 758 | fn string(&mut self, string: &ParseTreeType<'t>) -> Result<()> { 759 | let context = function_name!(); 760 | trace!("{}", self.trace_item_stack(context)); 761 | let string = string.token()?.clone(); 762 | let string_built = String { string }; 763 | // Calling user action here 764 | self.user_grammar.string(&string_built)?; 765 | self.push(ASTType::String(string_built), context); 766 | Ok(()) 767 | } 768 | 769 | /// Semantic action for production 20: 770 | /// 771 | /// `Number: /-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][-+]?(0|[1-9][0-9]*)?)?/;` 772 | /// 773 | #[parol_runtime::function_name::named] 774 | fn number(&mut self, number: &ParseTreeType<'t>) -> Result<()> { 775 | let context = function_name!(); 776 | trace!("{}", self.trace_item_stack(context)); 777 | let number = number.token()?.clone(); 778 | let number_built = Number { number }; 779 | // Calling user action here 780 | self.user_grammar.number(&number_built)?; 781 | self.push(ASTType::Number(number_built), context); 782 | Ok(()) 783 | } 784 | } 785 | 786 | impl<'t> UserActionsTrait<'t> for GrammarAuto<'t, '_> { 787 | /// 788 | /// This function is implemented automatically for the user's item Grammar. 789 | /// 790 | fn call_semantic_action_for_production_number( 791 | &mut self, 792 | prod_num: usize, 793 | children: &[ParseTreeType<'t>], 794 | ) -> Result<()> { 795 | match prod_num { 796 | 0 => self.json(&children[0]), 797 | 1 => self.object(&children[0], &children[1]), 798 | 2 => self.object_suffix_0(&children[0], &children[1], &children[2]), 799 | 3 => self.object_suffix_1(&children[0]), 800 | 4 => self.object_list_0(&children[0], &children[1], &children[2]), 801 | 5 => self.object_list_1(), 802 | 6 => self.pair(&children[0], &children[1], &children[2]), 803 | 7 => self.array(&children[0], &children[1]), 804 | 8 => self.array_suffix_0(&children[0], &children[1], &children[2]), 805 | 9 => self.array_suffix_1(&children[0]), 806 | 10 => self.array_list_0(&children[0], &children[1], &children[2]), 807 | 11 => self.array_list_1(), 808 | 12 => self.value_0(&children[0]), 809 | 13 => self.value_1(&children[0]), 810 | 14 => self.value_2(&children[0]), 811 | 15 => self.value_3(&children[0]), 812 | 16 => self.value_4(&children[0]), 813 | 17 => self.value_5(&children[0]), 814 | 18 => self.value_6(&children[0]), 815 | 19 => self.string(&children[0]), 816 | 20 => self.number(&children[0]), 817 | _ => Err(ParserError::InternalError(format!( 818 | "Unhandled production number: {}", 819 | prod_num 820 | )) 821 | .into()), 822 | } 823 | } 824 | 825 | fn on_comment(&mut self, token: Token<'t>) { 826 | self.user_grammar.on_comment(token) 827 | } 828 | } 829 | -------------------------------------------------------------------------------- /examples/parol-app/json.par: -------------------------------------------------------------------------------- 1 | 2 | %start Json 3 | %title "Json grammar" 4 | %comment "Derived from http://Json.org for parol by Joerg Singer." 5 | 6 | %% 7 | 8 | Json: Value 9 | ; 10 | 11 | Object 12 | : '{'^ Pair { ','^ Pair } '}'^ 13 | | '{'^ '}'^ 14 | ; 15 | 16 | Pair: String ':'^ Value 17 | ; 18 | 19 | Array 20 | : '['^ Value { ','^ Value } ']'^ 21 | | '['^ ']'^ 22 | ; 23 | 24 | Value 25 | : String 26 | | Number 27 | | Object 28 | | Array 29 | | 'true'^ 30 | | 'false'^ 31 | | 'null'^ 32 | ; 33 | 34 | String 35 | : /"(\\.|[^"])*"/ 36 | ; 37 | 38 | Number 39 | : /-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][-+]?(0|[1-9][0-9]*)?)?/ 40 | ; 41 | -------------------------------------------------------------------------------- /examples/parol-app/parser.rs: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------- 2 | // This file was generated by parol. 3 | // It is not intended for manual editing and changes will be 4 | // lost after next build. 5 | // --------------------------------------------------------- 6 | 7 | use parol_runtime::once_cell::sync::Lazy; 8 | #[allow(unused_imports)] 9 | use parol_runtime::parser::{LLKParser, LookaheadDFA, ParseTreeType, ParseType, Production, Trans}; 10 | use parol_runtime::{ParolError, ParseTree, TerminalIndex}; 11 | use parol_runtime::{ScannerConfig, TokenStream, Tokenizer}; 12 | use std::path::Path; 13 | 14 | use crate::grammar::Grammar; 15 | use crate::grammar_trait::GrammarAuto; 16 | 17 | use parol_runtime::lexer::tokenizer::{ 18 | ERROR_TOKEN, NEW_LINE_TOKEN, UNMATCHABLE_TOKEN, WHITESPACE_TOKEN, 19 | }; 20 | 21 | pub const TERMINALS: &[(&str, Option<(bool, &str)>); 17] = &[ 22 | /* 0 */ (UNMATCHABLE_TOKEN, None), 23 | /* 1 */ (UNMATCHABLE_TOKEN, None), 24 | /* 2 */ (UNMATCHABLE_TOKEN, None), 25 | /* 3 */ (UNMATCHABLE_TOKEN, None), 26 | /* 4 */ (UNMATCHABLE_TOKEN, None), 27 | /* 5 */ (r"\{", None), 28 | /* 6 */ (r"\}", None), 29 | /* 7 */ (r",", None), 30 | /* 8 */ (r":", None), 31 | /* 9 */ (r"\[", None), 32 | /* 10 */ (r"\]", None), 33 | /* 11 */ (r"true", None), 34 | /* 12 */ (r"false", None), 35 | /* 13 */ (r"null", None), 36 | /* 14 */ (r#""(\\.|[^"])*""#, None), 37 | /* 15 */ 38 | ( 39 | r"-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][-+]?(0|[1-9][0-9]*)?)?", 40 | None, 41 | ), 42 | /* 16 */ (ERROR_TOKEN, None), 43 | ]; 44 | 45 | pub const TERMINAL_NAMES: &[&str; 17] = &[ 46 | /* 0 */ "EndOfInput", 47 | /* 1 */ "Newline", 48 | /* 2 */ "Whitespace", 49 | /* 3 */ "LineComment", 50 | /* 4 */ "BlockComment", 51 | /* 5 */ "LBrace", 52 | /* 6 */ "RBrace", 53 | /* 7 */ "Comma", 54 | /* 8 */ "Colon", 55 | /* 9 */ "LBracket", 56 | /* 10 */ "RBracket", 57 | /* 11 */ "True", 58 | /* 12 */ "False", 59 | /* 13 */ "Null", 60 | /* 14 */ "String", 61 | /* 15 */ "Number", 62 | /* 16 */ "Error", 63 | ]; 64 | 65 | /* SCANNER_0: "INITIAL" */ 66 | const SCANNER_0: (&[&str; 5], &[TerminalIndex; 11]) = ( 67 | &[ 68 | /* 0 */ UNMATCHABLE_TOKEN, 69 | /* 1 */ NEW_LINE_TOKEN, 70 | /* 2 */ WHITESPACE_TOKEN, 71 | /* 3 */ UNMATCHABLE_TOKEN, 72 | /* 4 */ UNMATCHABLE_TOKEN, 73 | ], 74 | &[ 75 | 5, /* LBrace */ 76 | 6, /* RBrace */ 77 | 7, /* Comma */ 78 | 8, /* Colon */ 79 | 9, /* LBracket */ 80 | 10, /* RBracket */ 81 | 11, /* True */ 82 | 12, /* False */ 83 | 13, /* Null */ 84 | 14, /* String */ 85 | 15, /* Number */ 86 | ], 87 | ); 88 | 89 | const MAX_K: usize = 1; 90 | 91 | pub const NON_TERMINALS: &[&str; 11] = &[ 92 | /* 0 */ "Array", 93 | /* 1 */ "ArrayList", 94 | /* 2 */ "ArraySuffix", 95 | /* 3 */ "Json", 96 | /* 4 */ "Number", 97 | /* 5 */ "Object", 98 | /* 6 */ "ObjectList", 99 | /* 7 */ "ObjectSuffix", 100 | /* 8 */ "Pair", 101 | /* 9 */ "String", 102 | /* 10 */ "Value", 103 | ]; 104 | 105 | pub const LOOKAHEAD_AUTOMATA: &[LookaheadDFA; 11] = &[ 106 | /* 0 - "Array" */ 107 | LookaheadDFA { 108 | prod0: 7, 109 | transitions: &[], 110 | k: 0, 111 | }, 112 | /* 1 - "ArrayList" */ 113 | LookaheadDFA { 114 | prod0: -1, 115 | transitions: &[Trans(0, 7, 1, 10), Trans(0, 10, 2, 11)], 116 | k: 1, 117 | }, 118 | /* 2 - "ArraySuffix" */ 119 | LookaheadDFA { 120 | prod0: -1, 121 | transitions: &[ 122 | Trans(0, 5, 1, 8), 123 | Trans(0, 9, 1, 8), 124 | Trans(0, 10, 2, 9), 125 | Trans(0, 11, 1, 8), 126 | Trans(0, 12, 1, 8), 127 | Trans(0, 13, 1, 8), 128 | Trans(0, 14, 1, 8), 129 | Trans(0, 15, 1, 8), 130 | ], 131 | k: 1, 132 | }, 133 | /* 3 - "Json" */ 134 | LookaheadDFA { 135 | prod0: 0, 136 | transitions: &[], 137 | k: 0, 138 | }, 139 | /* 4 - "Number" */ 140 | LookaheadDFA { 141 | prod0: 20, 142 | transitions: &[], 143 | k: 0, 144 | }, 145 | /* 5 - "Object" */ 146 | LookaheadDFA { 147 | prod0: 1, 148 | transitions: &[], 149 | k: 0, 150 | }, 151 | /* 6 - "ObjectList" */ 152 | LookaheadDFA { 153 | prod0: -1, 154 | transitions: &[Trans(0, 6, 2, 5), Trans(0, 7, 1, 4)], 155 | k: 1, 156 | }, 157 | /* 7 - "ObjectSuffix" */ 158 | LookaheadDFA { 159 | prod0: -1, 160 | transitions: &[Trans(0, 6, 2, 3), Trans(0, 14, 1, 2)], 161 | k: 1, 162 | }, 163 | /* 8 - "Pair" */ 164 | LookaheadDFA { 165 | prod0: 6, 166 | transitions: &[], 167 | k: 0, 168 | }, 169 | /* 9 - "String" */ 170 | LookaheadDFA { 171 | prod0: 19, 172 | transitions: &[], 173 | k: 0, 174 | }, 175 | /* 10 - "Value" */ 176 | LookaheadDFA { 177 | prod0: -1, 178 | transitions: &[ 179 | Trans(0, 5, 3, 14), 180 | Trans(0, 9, 4, 15), 181 | Trans(0, 11, 5, 16), 182 | Trans(0, 12, 6, 17), 183 | Trans(0, 13, 7, 18), 184 | Trans(0, 14, 1, 12), 185 | Trans(0, 15, 2, 13), 186 | ], 187 | k: 1, 188 | }, 189 | ]; 190 | 191 | pub const PRODUCTIONS: &[Production; 21] = &[ 192 | // 0 - Json: Value; 193 | Production { 194 | lhs: 3, 195 | production: &[ParseType::N(10)], 196 | }, 197 | // 1 - Object: '{'^ /* Clipped */ ObjectSuffix; 198 | Production { 199 | lhs: 5, 200 | production: &[ParseType::N(7), ParseType::T(5)], 201 | }, 202 | // 2 - ObjectSuffix: Pair ObjectList /* Vec */ '}'^ /* Clipped */; 203 | Production { 204 | lhs: 7, 205 | production: &[ParseType::T(6), ParseType::N(6), ParseType::N(8)], 206 | }, 207 | // 3 - ObjectSuffix: '}'^ /* Clipped */; 208 | Production { 209 | lhs: 7, 210 | production: &[ParseType::T(6)], 211 | }, 212 | // 4 - ObjectList: ','^ /* Clipped */ Pair ObjectList; 213 | Production { 214 | lhs: 6, 215 | production: &[ParseType::N(6), ParseType::N(8), ParseType::T(7)], 216 | }, 217 | // 5 - ObjectList: ; 218 | Production { 219 | lhs: 6, 220 | production: &[], 221 | }, 222 | // 6 - Pair: String ':'^ /* Clipped */ Value; 223 | Production { 224 | lhs: 8, 225 | production: &[ParseType::N(10), ParseType::T(8), ParseType::N(9)], 226 | }, 227 | // 7 - Array: '['^ /* Clipped */ ArraySuffix; 228 | Production { 229 | lhs: 0, 230 | production: &[ParseType::N(2), ParseType::T(9)], 231 | }, 232 | // 8 - ArraySuffix: Value ArrayList /* Vec */ ']'^ /* Clipped */; 233 | Production { 234 | lhs: 2, 235 | production: &[ParseType::T(10), ParseType::N(1), ParseType::N(10)], 236 | }, 237 | // 9 - ArraySuffix: ']'^ /* Clipped */; 238 | Production { 239 | lhs: 2, 240 | production: &[ParseType::T(10)], 241 | }, 242 | // 10 - ArrayList: ','^ /* Clipped */ Value ArrayList; 243 | Production { 244 | lhs: 1, 245 | production: &[ParseType::N(1), ParseType::N(10), ParseType::T(7)], 246 | }, 247 | // 11 - ArrayList: ; 248 | Production { 249 | lhs: 1, 250 | production: &[], 251 | }, 252 | // 12 - Value: String; 253 | Production { 254 | lhs: 10, 255 | production: &[ParseType::N(9)], 256 | }, 257 | // 13 - Value: Number; 258 | Production { 259 | lhs: 10, 260 | production: &[ParseType::N(4)], 261 | }, 262 | // 14 - Value: Object; 263 | Production { 264 | lhs: 10, 265 | production: &[ParseType::N(5)], 266 | }, 267 | // 15 - Value: Array; 268 | Production { 269 | lhs: 10, 270 | production: &[ParseType::N(0)], 271 | }, 272 | // 16 - Value: 'true'^ /* Clipped */; 273 | Production { 274 | lhs: 10, 275 | production: &[ParseType::T(11)], 276 | }, 277 | // 17 - Value: 'false'^ /* Clipped */; 278 | Production { 279 | lhs: 10, 280 | production: &[ParseType::T(12)], 281 | }, 282 | // 18 - Value: 'null'^ /* Clipped */; 283 | Production { 284 | lhs: 10, 285 | production: &[ParseType::T(13)], 286 | }, 287 | // 19 - String: /"(\\.|[^"])*"/; 288 | Production { 289 | lhs: 9, 290 | production: &[ParseType::T(14)], 291 | }, 292 | // 20 - Number: /-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][-+]?(0|[1-9][0-9]*)?)?/; 293 | Production { 294 | lhs: 4, 295 | production: &[ParseType::T(15)], 296 | }, 297 | ]; 298 | 299 | static SCANNERS: Lazy> = Lazy::new(|| { 300 | vec![ScannerConfig::new( 301 | "INITIAL", 302 | Tokenizer::build(TERMINALS, SCANNER_0.0, SCANNER_0.1).unwrap(), 303 | &[], 304 | )] 305 | }); 306 | 307 | pub fn parse<'t, T>( 308 | input: &'t str, 309 | file_name: T, 310 | user_actions: &mut Grammar<'t>, 311 | ) -> Result 312 | where 313 | T: AsRef, 314 | { 315 | let mut llk_parser = LLKParser::new( 316 | 3, 317 | LOOKAHEAD_AUTOMATA, 318 | PRODUCTIONS, 319 | TERMINAL_NAMES, 320 | NON_TERMINALS, 321 | ); 322 | llk_parser.trim_parse_tree(); 323 | 324 | // Initialize wrapper 325 | let mut user_actions = GrammarAuto::new(user_actions); 326 | llk_parser.parse( 327 | TokenStream::new(input, file_name, &SCANNERS, MAX_K).unwrap(), 328 | &mut user_actions, 329 | ) 330 | } 331 | -------------------------------------------------------------------------------- /examples/parol-app/tests/codegen.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn codegen() { 3 | use snapbox::assert_data_eq; 4 | use snapbox::Data; 5 | 6 | let tmp_dir = env!("CARGO_TARGET_TMPDIR"); 7 | let mut output_dir = std::path::PathBuf::from(tmp_dir); 8 | output_dir.push("parol"); 9 | std::fs::create_dir_all(&output_dir).unwrap(); 10 | 11 | let expected_root = std::path::Path::new("."); 12 | 13 | let mut builder = parol::build::Builder::with_explicit_output_dir(&output_dir); 14 | builder.grammar_file("json.par"); 15 | builder.parser_output_file("parser.rs"); 16 | builder.actions_output_file("grammar_trait.rs"); 17 | builder.trim_parse_tree(); 18 | builder.generate_parser().unwrap(); 19 | 20 | for entry in std::fs::read_dir(&output_dir).unwrap() { 21 | let entry = entry.unwrap(); 22 | let actual_path = entry.path(); 23 | let actual_name = entry.file_name(); 24 | let actual = std::fs::read_to_string(&actual_path).unwrap(); 25 | let expected_path = expected_root.join(actual_name); 26 | assert_data_eq!(actual, Data::read_from(&expected_path, None).raw()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/peg-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "peg-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "peg-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | peg = "0.8.5" 11 | 12 | [lints] 13 | workspace = true 14 | -------------------------------------------------------------------------------- /examples/peg-app/app.rs: -------------------------------------------------------------------------------- 1 | mod parser; 2 | 3 | use std::{env, fs}; 4 | 5 | fn main() { 6 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 7 | .expect("Failed to read file"); 8 | 9 | match parser::parser::json(&src) { 10 | Ok(json) => { 11 | #[cfg(debug_assertions)] 12 | { 13 | println!("{:#?}", json); 14 | } 15 | #[cfg(not(debug_assertions))] 16 | { 17 | std::hint::black_box(json); 18 | } 19 | } 20 | Err(err) => { 21 | eprintln!("{}", err); 22 | std::process::exit(1); 23 | } 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /examples/peg-app/parser.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, collections::HashMap, str::FromStr}; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub enum JsonValue { 5 | Null, 6 | Boolean(bool), 7 | Str(String), 8 | Num(f64), 9 | Array(Vec), 10 | Object(HashMap), 11 | } 12 | 13 | peg::parser!(pub grammar parser() for str { 14 | 15 | pub rule json() -> JsonValue 16 | = _ value:value() _ { value } 17 | 18 | rule _() = [' ' | '\t' | '\r' | '\n']* 19 | rule value_separator() = _ "," _ 20 | 21 | rule value() -> JsonValue 22 | = boolean() / null() / object() / array() / number() / string() 23 | 24 | rule null() -> JsonValue 25 | = "null" { JsonValue::Null } 26 | 27 | rule boolean() -> JsonValue 28 | = "true" { JsonValue::Boolean(true) } 29 | / "false" { JsonValue::Boolean(false) } 30 | 31 | rule object() -> JsonValue 32 | = "{" _ elements:(member() ** value_separator()) _ "}" { 33 | JsonValue::Object(elements.into_iter().collect()) 34 | } 35 | 36 | rule member() -> (String, JsonValue) 37 | = key:raw_string() _ ":" _ value:value() { (key, value) } 38 | 39 | rule array() -> JsonValue 40 | = "[" _ elements:(value() ** value_separator()) _ "]" { 41 | JsonValue::Array(elements) 42 | } 43 | 44 | rule string() -> JsonValue 45 | = value:raw_string() { JsonValue::Str(value) } 46 | 47 | rule raw_string() -> String 48 | = "\"" slices:string_slice()* "\"" { slices.concat() } 49 | 50 | /// A substring of same-kind (escaped or unescaped) characters 51 | rule string_slice() -> Cow<'input, str> 52 | = value:string_characters() { Cow::Borrowed(value) } 53 | / value:string_escapes() { Cow::Owned(value.into_iter().collect()) } 54 | 55 | /// A substring of unescaped characters 56 | rule string_characters() -> &'input str 57 | = $([^ '\"' | '\\']+) 58 | 59 | /// A substring of escaped characters 60 | rule string_escapes() -> Vec 61 | = ("\\" value:string_escape_char() { value })+ 62 | 63 | /// Handles a single escape 64 | rule string_escape_char() -> char 65 | = "\"" { '"' } 66 | / "\\" { '\\' } 67 | / "/" { '/' } 68 | / "b" { '\x08' } 69 | / "f" { '\x0C' } 70 | / "n" { '\n' } 71 | / "r" { '\r' } 72 | / "t" { '\t' } 73 | / "u" digits:$(hex_digit()*<4>) { ? 74 | let value = u16::from_str_radix(digits, 16).unwrap(); 75 | char::from_u32(value.into()).ok_or("invalid unicode escape") 76 | } 77 | 78 | rule hex_digit() 79 | = ['0'..='9' | 'a'..='f' | 'A'..='F'] 80 | 81 | rule number() -> JsonValue 82 | = "-"? value:$(int() frac()? exp()?) { ? 83 | Ok(JsonValue::Num(f64::from_str(value).map_err(|_| "invalid number")?)) 84 | } 85 | 86 | rule int() 87 | = ['0'] / ['1'..='9']['0'..='9']* 88 | 89 | rule exp() 90 | = ("e" / "E") ("-" / "+")? ['0'..='9']*<1,> 91 | 92 | rule frac() 93 | = "." ['0'..='9']*<1,> 94 | }); 95 | -------------------------------------------------------------------------------- /examples/pest-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pest-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "pest-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | pest = "2.8.0" 11 | pest_derive = "2.8.0" 12 | 13 | [lints] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/pest-app/app.rs: -------------------------------------------------------------------------------- 1 | // pest. The Elegant Parser 2 | // Copyright (c) 2018 Dragoș Tiselice 3 | // 4 | // Licensed under the Apache License, Version 2.0 5 | // or the MIT 6 | // license , at your 7 | // option. All files in the project carrying such notice may not be copied, 8 | // modified, or distributed except according to those terms. 9 | 10 | mod parser; 11 | 12 | use std::{env, fs}; 13 | 14 | fn main() { 15 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 16 | .expect("Failed to read file"); 17 | 18 | match parser::parse_json_file(&src) { 19 | Ok(json) => { 20 | #[cfg(debug_assertions)] 21 | { 22 | println!("{:#?}", json); 23 | } 24 | #[cfg(not(debug_assertions))] 25 | { 26 | std::hint::black_box(json); 27 | } 28 | } 29 | Err(err) => { 30 | eprintln!("{}", err); 31 | std::process::exit(1); 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /examples/pest-app/json.pest: -------------------------------------------------------------------------------- 1 | json = _{ SOI ~ (object | array) ~ EOI } 2 | 3 | value = _{ object | array | string | number | boolean | null } 4 | 5 | object = { 6 | "{" ~ "}" | 7 | "{" ~ pair ~ ("," ~ pair)* ~ "}" 8 | } 9 | pair = { string ~ ":" ~ value } 10 | 11 | array = { 12 | "[" ~ "]" | 13 | "[" ~ value ~ ("," ~ value)* ~ "]" 14 | } 15 | 16 | string = ${ "\"" ~ inner ~ "\"" } 17 | inner = @{ char* } 18 | char = { 19 | !("\"" | "\\") ~ ANY 20 | | "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t") 21 | | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4}) 22 | } 23 | 24 | number = @{ 25 | "-"? 26 | ~ ("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) 27 | ~ ("." ~ ASCII_DIGIT*)? 28 | ~ (^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+)? 29 | } 30 | 31 | boolean = { "true" | "false" } 32 | 33 | null = { "null" } 34 | 35 | WHITESPACE = _{ " " | "\t" | "\r" | "\n" } 36 | -------------------------------------------------------------------------------- /examples/pest-app/parser.rs: -------------------------------------------------------------------------------- 1 | // pest. The Elegant Parser 2 | // Copyright (c) 2018 Dragoș Tiselice 3 | // 4 | // Licensed under the Apache License, Version 2.0 5 | // or the MIT 6 | // license , at your 7 | // option. All files in the project carrying such notice may not be copied, 8 | // modified, or distributed except according to those terms. 9 | 10 | use std::collections::HashMap; 11 | 12 | use pest::error::Error; 13 | use pest::Parser; 14 | use pest_derive::Parser; 15 | 16 | #[derive(Parser)] 17 | #[grammar = "json.pest"] 18 | struct JSONParser; 19 | 20 | #[derive(Debug, PartialEq)] 21 | pub enum Json<'i> { 22 | Null, 23 | Bool(bool), 24 | Number(f64), 25 | String(&'i str), 26 | Array(Vec>), 27 | Object(HashMap<&'i str, Json<'i>>), 28 | } 29 | 30 | pub fn parse_json_file(input: &str) -> Result, Error> { 31 | use pest::iterators::Pair; 32 | 33 | let json = JSONParser::parse(Rule::json, input)?.next().unwrap(); 34 | 35 | fn parse_value(pair: Pair) -> Json { 36 | match pair.as_rule() { 37 | Rule::object => Json::Object( 38 | pair.into_inner() 39 | .map(|pair| { 40 | let mut inner_rules = pair.into_inner(); 41 | let name = inner_rules 42 | .next() 43 | .unwrap() 44 | .into_inner() 45 | .next() 46 | .unwrap() 47 | .as_str(); 48 | let value = parse_value(inner_rules.next().unwrap()); 49 | (name, value) 50 | }) 51 | .collect(), 52 | ), 53 | Rule::array => Json::Array(pair.into_inner().map(parse_value).collect()), 54 | Rule::string => Json::String(pair.into_inner().next().unwrap().as_str()), 55 | Rule::number => Json::Number(pair.as_str().parse().unwrap()), 56 | Rule::boolean => Json::Bool(pair.as_str().parse().unwrap()), 57 | Rule::null => Json::Null, 58 | Rule::json 59 | | Rule::EOI 60 | | Rule::pair 61 | | Rule::value 62 | | Rule::inner 63 | | Rule::char 64 | | Rule::WHITESPACE => unreachable!(), 65 | } 66 | } 67 | 68 | Ok(parse_value(json)) 69 | } 70 | -------------------------------------------------------------------------------- /examples/serde_json-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_json-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "serde_json-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | serde_json = "1.0.140" 11 | 12 | [lints] 13 | workspace = true 14 | -------------------------------------------------------------------------------- /examples/serde_json-app/app.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 5 | .expect("Failed to read file"); 6 | 7 | match serde_json::from_str::(&src) { 8 | Ok(json) => { 9 | #[cfg(debug_assertions)] 10 | { 11 | println!("{:#?}", json); 12 | } 13 | #[cfg(not(debug_assertions))] 14 | { 15 | std::hint::black_box(json); 16 | } 17 | } 18 | Err(e) => eprintln!("{e}"), 19 | } 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /examples/winnow-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "winnow-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "winnow-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | winnow = "0.7.8" 11 | 12 | [lints] 13 | workspace = true 14 | -------------------------------------------------------------------------------- /examples/winnow-app/app.rs: -------------------------------------------------------------------------------- 1 | mod json; 2 | mod parser; 3 | 4 | use std::{env, fs}; 5 | 6 | use winnow::error::ContextError; 7 | use winnow::prelude::*; 8 | 9 | fn main() { 10 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 11 | .expect("Failed to read file"); 12 | 13 | match parser::json:: 14 | .parse(src.as_str()) 15 | .map_err(|e| e.to_string()) 16 | { 17 | Ok(json) => { 18 | #[cfg(debug_assertions)] 19 | { 20 | println!("{:#?}", json); 21 | } 22 | #[cfg(not(debug_assertions))] 23 | { 24 | std::hint::black_box(json); 25 | } 26 | } 27 | Err(err) => { 28 | eprintln!("{}", err); 29 | std::process::exit(1); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/winnow-app/json.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub enum JsonValue { 5 | Null, 6 | Boolean(bool), 7 | Str(String), 8 | Num(f64), 9 | Array(Vec), 10 | Object(HashMap), 11 | } 12 | -------------------------------------------------------------------------------- /examples/winnow-app/parser.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::str; 3 | 4 | use winnow::prelude::*; 5 | use winnow::Result; 6 | use winnow::{ 7 | ascii::float, 8 | combinator::empty, 9 | combinator::fail, 10 | combinator::peek, 11 | combinator::{alt, dispatch}, 12 | combinator::{delimited, preceded, separated_pair, terminated}, 13 | combinator::{repeat, separated}, 14 | error::{AddContext, ParserError, StrContext}, 15 | token::{any, none_of, take, take_while}, 16 | }; 17 | 18 | use crate::json::JsonValue; 19 | 20 | pub type Stream<'i> = &'i str; 21 | 22 | /// The root element of a JSON parser is any value 23 | /// 24 | /// A parser has the following signature: 25 | /// `&mut Stream -> Result` 26 | /// 27 | /// most of the times you can ignore the error type and use the default (but this 28 | /// examples shows custom error types later on!) 29 | /// 30 | /// Here we use `&str` as input type, but parsers can be generic over 31 | /// the input type, work directly with `&[u8]`, or any other type that 32 | /// implements the required traits. 33 | pub fn json<'i, E: ParserError> + AddContext, StrContext>>( 34 | input: &mut Stream<'i>, 35 | ) -> Result { 36 | delimited(ws, json_value, ws).parse_next(input) 37 | } 38 | 39 | /// `alt` is a combinator that tries multiple parsers one by one, until 40 | /// one of them succeeds 41 | fn json_value<'i, E: ParserError> + AddContext, StrContext>>( 42 | input: &mut Stream<'i>, 43 | ) -> Result { 44 | // `dispatch` gives you `match`-like behavior compared to `alt` successively trying different 45 | // implementations. 46 | dispatch!(peek(any); 47 | 'n' => null.value(JsonValue::Null), 48 | 't' => true_.map(JsonValue::Boolean), 49 | 'f' => false_.map(JsonValue::Boolean), 50 | '"' => string.map(JsonValue::Str), 51 | '+' => float.map(JsonValue::Num), 52 | '-' => float.map(JsonValue::Num), 53 | '0'..='9' => float.map(JsonValue::Num), 54 | '[' => array.map(JsonValue::Array), 55 | '{' => object.map(JsonValue::Object), 56 | _ => fail, 57 | ) 58 | .parse_next(input) 59 | } 60 | 61 | /// `literal(string)` generates a parser that takes the argument string. 62 | /// 63 | /// This also shows returning a sub-slice of the original input 64 | fn null<'i, E: ParserError>>(input: &mut Stream<'i>) -> Result<&'i str, E> { 65 | // This is a parser that returns `"null"` if it sees the string "null", and 66 | // an error otherwise 67 | "null".parse_next(input) 68 | } 69 | 70 | /// We can combine `tag` with other functions, like `value` which returns a given constant value on 71 | /// success. 72 | fn true_<'i, E: ParserError>>(input: &mut Stream<'i>) -> Result { 73 | // This is a parser that returns `true` if it sees the string "true", and 74 | // an error otherwise 75 | "true".value(true).parse_next(input) 76 | } 77 | 78 | /// We can combine `tag` with other functions, like `value` which returns a given constant value on 79 | /// success. 80 | fn false_<'i, E: ParserError>>(input: &mut Stream<'i>) -> Result { 81 | // This is a parser that returns `false` if it sees the string "false", and 82 | // an error otherwise 83 | "false".value(false).parse_next(input) 84 | } 85 | 86 | /// This parser gathers all `char`s up into a `String`with a parse to take the double quote 87 | /// character, before the string (using `preceded`) and after the string (using `terminated`). 88 | fn string<'i, E: ParserError> + AddContext, StrContext>>( 89 | input: &mut Stream<'i>, 90 | ) -> Result { 91 | preceded( 92 | '\"', 93 | terminated( 94 | repeat(0.., character).fold(String::new, |mut string, c| { 95 | string.push(c); 96 | string 97 | }), 98 | '\"', 99 | ), 100 | ) 101 | // `context` lets you add a static string to errors to provide more information in the 102 | // error chain (to indicate which parser had an error) 103 | .context(StrContext::Expected("string".into())) 104 | .parse_next(input) 105 | } 106 | 107 | /// You can mix the above declarative parsing with an imperative style to handle more unique cases, 108 | /// like escaping 109 | fn character<'i, E: ParserError>>(input: &mut Stream<'i>) -> Result { 110 | let c = none_of('\"').parse_next(input)?; 111 | if c == '\\' { 112 | dispatch!(any; 113 | '"' => empty.value('"'), 114 | '\\' => empty.value('\\'), 115 | '/' => empty.value('/'), 116 | 'b' => empty.value('\x08'), 117 | 'f' => empty.value('\x0C'), 118 | 'n' => empty.value('\n'), 119 | 'r' => empty.value('\r'), 120 | 't' => empty.value('\t'), 121 | 'u' => unicode_escape, 122 | _ => fail, 123 | ) 124 | .parse_next(input) 125 | } else { 126 | Ok(c) 127 | } 128 | } 129 | 130 | fn unicode_escape<'i, E: ParserError>>(input: &mut Stream<'i>) -> Result { 131 | alt(( 132 | // Not a surrogate 133 | u16_hex 134 | .verify(|cp| !(0xD800..0xE000).contains(cp)) 135 | .map(|cp| cp as u32), 136 | // See https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF for details 137 | separated_pair(u16_hex, "\\u", u16_hex) 138 | .verify(|(high, low)| (0xD800..0xDC00).contains(high) && (0xDC00..0xE000).contains(low)) 139 | .map(|(high, low)| { 140 | let high_ten = (high as u32) - 0xD800; 141 | let low_ten = (low as u32) - 0xDC00; 142 | (high_ten << 10) + low_ten + 0x10000 143 | }), 144 | )) 145 | .verify_map( 146 | // Could be probably replaced with .unwrap() or _unchecked due to the verify checks 147 | std::char::from_u32, 148 | ) 149 | .parse_next(input) 150 | } 151 | 152 | fn u16_hex<'i, E: ParserError>>(input: &mut Stream<'i>) -> Result { 153 | take(4usize) 154 | .verify_map(|s| u16::from_str_radix(s, 16).ok()) 155 | .parse_next(input) 156 | } 157 | 158 | /// Some combinators, like `separated` or `repeat`, will call a parser repeatedly, 159 | /// accumulating results in a `Vec`, until it encounters an error. 160 | /// If you want more control on the parser application, check out the `iterator` 161 | /// combinator (cf `examples/iterator.rs`) 162 | fn array<'i, E: ParserError> + AddContext, StrContext>>( 163 | input: &mut Stream<'i>, 164 | ) -> Result, E> { 165 | preceded( 166 | ('[', ws), 167 | terminated(separated(0.., json_value, (ws, ',', ws)), (ws, ']')), 168 | ) 169 | .context(StrContext::Expected("array".into())) 170 | .parse_next(input) 171 | } 172 | 173 | fn object<'i, E: ParserError> + AddContext, StrContext>>( 174 | input: &mut Stream<'i>, 175 | ) -> Result, E> { 176 | preceded( 177 | ('{', ws), 178 | terminated(separated(0.., key_value, (ws, ',', ws)), (ws, '}')), 179 | ) 180 | .context(StrContext::Expected("object".into())) 181 | .parse_next(input) 182 | } 183 | 184 | fn key_value<'i, E: ParserError> + AddContext, StrContext>>( 185 | input: &mut Stream<'i>, 186 | ) -> Result<(String, JsonValue), E> { 187 | separated_pair(string, (ws, ':', ws), json_value).parse_next(input) 188 | } 189 | 190 | /// Parser combinators are constructed from the bottom up: 191 | /// first we write parsers for the smallest elements (here a space character), 192 | /// then we'll combine them in larger parsers 193 | fn ws<'i, E: ParserError>>(input: &mut Stream<'i>) -> Result<&'i str, E> { 194 | // Combinators like `take_while` return a function. That function is the 195 | // parser,to which we can pass the input 196 | take_while(0.., WS).parse_next(input) 197 | } 198 | 199 | const WS: &[char] = &[' ', '\t', '\r', '\n']; 200 | 201 | #[cfg(test)] 202 | mod test { 203 | #[allow(clippy::useless_attribute)] 204 | #[allow(unused_imports)] // its dead for benches 205 | use super::*; 206 | 207 | #[allow(clippy::useless_attribute)] 208 | #[allow(dead_code)] // its dead for benches 209 | type Error = winnow::error::ContextError; 210 | 211 | #[test] 212 | fn json_string() { 213 | assert_eq!(string::.parse_peek("\"\""), Ok(("", "".to_owned()))); 214 | assert_eq!( 215 | string::.parse_peek("\"abc\""), 216 | Ok(("", "abc".to_owned())) 217 | ); 218 | assert_eq!( 219 | string:: 220 | .parse_peek("\"abc\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0001\\u2014\u{2014}def\""), 221 | Ok(("", "abc\"\\/\x08\x0C\n\r\t\x01——def".to_owned())), 222 | ); 223 | assert_eq!( 224 | string::.parse_peek("\"\\uD83D\\uDE10\""), 225 | Ok(("", "😐".to_owned())) 226 | ); 227 | 228 | assert!(string::.parse_peek("\"").is_err()); 229 | assert!(string::.parse_peek("\"abc").is_err()); 230 | assert!(string::.parse_peek("\"\\\"").is_err()); 231 | assert!(string::.parse_peek("\"\\u123\"").is_err()); 232 | assert!(string::.parse_peek("\"\\uD800\"").is_err()); 233 | assert!(string::.parse_peek("\"\\uD800\\uD800\"").is_err()); 234 | assert!(string::.parse_peek("\"\\uDC00\"").is_err()); 235 | } 236 | 237 | #[test] 238 | fn json_object() { 239 | use JsonValue::{Num, Object, Str}; 240 | 241 | let input = r#"{"a":42,"b":"x"}"#; 242 | 243 | let expected = Object( 244 | vec![ 245 | ("a".to_owned(), Num(42.0)), 246 | ("b".to_owned(), Str("x".to_owned())), 247 | ] 248 | .into_iter() 249 | .collect(), 250 | ); 251 | 252 | assert_eq!(json::.parse_peek(input), Ok(("", expected))); 253 | } 254 | 255 | #[test] 256 | fn json_array() { 257 | use JsonValue::{Array, Num, Str}; 258 | 259 | let input = r#"[42,"x"]"#; 260 | 261 | let expected = Array(vec![Num(42.0), Str("x".to_owned())]); 262 | 263 | assert_eq!(json::.parse_peek(input), Ok(("", expected))); 264 | } 265 | 266 | #[test] 267 | fn json_whitespace() { 268 | use JsonValue::{Array, Boolean, Null, Num, Object, Str}; 269 | 270 | let input = r#" 271 | { 272 | "null" : null, 273 | "true" :true , 274 | "false": false , 275 | "number" : 123e4 , 276 | "string" : " abc 123 " , 277 | "array" : [ false , 1 , "two" ] , 278 | "object" : { "a" : 1.0 , "b" : "c" } , 279 | "empty_array" : [ ] , 280 | "empty_object" : { } 281 | } 282 | "#; 283 | 284 | assert_eq!( 285 | json::.parse_peek(input), 286 | Ok(( 287 | "", 288 | Object( 289 | vec![ 290 | ("null".to_owned(), Null), 291 | ("true".to_owned(), Boolean(true)), 292 | ("false".to_owned(), Boolean(false)), 293 | ("number".to_owned(), Num(123e4)), 294 | ("string".to_owned(), Str(" abc 123 ".to_owned())), 295 | ( 296 | "array".to_owned(), 297 | Array(vec![Boolean(false), Num(1.0), Str("two".to_owned())]) 298 | ), 299 | ( 300 | "object".to_owned(), 301 | Object( 302 | vec![ 303 | ("a".to_owned(), Num(1.0)), 304 | ("b".to_owned(), Str("c".to_owned())), 305 | ] 306 | .into_iter() 307 | .collect() 308 | ) 309 | ), 310 | ("empty_array".to_owned(), Array(vec![]),), 311 | ("empty_object".to_owned(), Object(HashMap::new()),), 312 | ] 313 | .into_iter() 314 | .collect() 315 | ) 316 | )) 317 | ); 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /examples/yap-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yap-app" 3 | edition.workspace = true 4 | 5 | [[bin]] 6 | name = "yap-app" 7 | path = "app.rs" 8 | 9 | [dependencies] 10 | yap = "0.12" 11 | 12 | [lints] 13 | workspace = true 14 | -------------------------------------------------------------------------------- /examples/yap-app/app.rs: -------------------------------------------------------------------------------- 1 | mod parser; 2 | 3 | use std::{env, fs}; 4 | 5 | fn main() { 6 | let src = fs::read_to_string(env::args().nth(1).expect("Expected file argument")) 7 | .expect("Failed to read file"); 8 | 9 | match parser::parse(&src) { 10 | Ok(json) => { 11 | #[cfg(debug_assertions)] 12 | { 13 | println!("{:#?}", json); 14 | } 15 | #[cfg(not(debug_assertions))] 16 | { 17 | std::hint::black_box(json); 18 | } 19 | } 20 | Err(err) => { 21 | eprintln!("{:?}", err); 22 | std::process::exit(1); 23 | } 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /examples/yap-app/parser.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use yap::{IntoTokens, TokenLocation, Tokens}; 3 | 4 | /// Parse JSON from a string. Just a very thin wrapper around `value()`. 5 | pub fn parse(s: &str) -> Result { 6 | value(&mut s.into_tokens()) 7 | } 8 | 9 | /// This is what we'll parse our JSON into. 10 | #[derive(Clone, PartialEq, Debug)] 11 | pub enum Value { 12 | Null, 13 | Number(f64), 14 | String(String), 15 | Bool(bool), 16 | Array(Vec), 17 | Object(HashMap), 18 | } 19 | 20 | /// Some errors that can be emitted if things go wrong. 21 | /// In this example, each error has a start and end location 22 | /// denoting where the issue is in the string. 23 | #[derive(PartialEq, Debug)] 24 | pub struct Error { 25 | // Start and end location of the error 26 | location: (usize, usize), 27 | // What was the nature of the error? 28 | kind: ErrorKind, 29 | } 30 | 31 | #[derive(PartialEq, Debug)] 32 | enum ErrorKind { 33 | // No ']' seen while parsing array. 34 | ArrayNotClosed, 35 | // No '}' seen while parsing object. 36 | ObjectNotClosed, 37 | // Object field isn't a valid string. 38 | InvalidObjectField, 39 | // No ':' seen between object field and valud. 40 | MissingObjectFieldSeparator, 41 | // String escape char (ie char after \) isn't valid. 42 | InvalidEscapeChar(char), 43 | // the file ended while we were still parsing. 44 | UnexpectedEof, 45 | // We didn't successfully parse any valid JSON at all. 46 | InvalidJson, 47 | } 48 | 49 | impl ErrorKind { 50 | fn at(self, start: T, end: T) -> Error { 51 | Error { 52 | location: (start.offset(), end.offset()), 53 | kind: self, 54 | } 55 | } 56 | } 57 | 58 | /// This is the `yap` entry point, and is responsible for parsing JSON values. 59 | /// 60 | /// Try parsing each of the different types of value we know about, 61 | /// and return the first error that we encounter, or a valid `Value`. 62 | fn value(toks: &mut impl Tokens) -> Result { 63 | // Return the first thing we parse successfully from our token stream, 64 | // mapping values into their `Value` container. 65 | let value = yap::one_of!(ts from toks; 66 | array(ts).map(|res| res.map(Value::Array)), 67 | string(ts).map(|res| res.map(Value::String)), 68 | object(ts).map(|res| res.map(Value::Object)), 69 | number(ts).map(|v| Ok(Value::Number(v))), 70 | bool(ts).map(|v| Ok(Value::Bool(v))), 71 | null(ts).then_some(Ok(Value::Null)) 72 | ); 73 | 74 | // No value? This means that the input doesn't begin with any valid JSON 75 | // character. 76 | match value { 77 | Some(r) => r, 78 | None => Err(ErrorKind::InvalidJson.at(toks.location(), toks.location())), 79 | } 80 | } 81 | 82 | /// Arrays start and end with [ and ], and contain JSON values, which we can 83 | /// use our top level value parser to handle. 84 | /// 85 | /// - `Some(Ok(values))` means we successfully parsed 0 or more array values. 86 | /// - `Some(Err(e))` means that we hit an error parsing the array. 87 | /// - `None` means that this wasn't an array and so nothing was parsed. 88 | fn array(toks: &mut impl Tokens) -> Option, Error>> { 89 | // Note the location of the start of the array. 90 | let start = toks.location(); 91 | 92 | // Try to consume a '['. If we can't, we consume nothing and bail. 93 | if !toks.token('[') { 94 | return None; 95 | } 96 | skip_whitespace(&mut *toks); 97 | 98 | // Use our `value()` parser to parse each array value, separated by ','. 99 | let values: Vec = toks 100 | .sep_by(|t| value(t).ok(), |t| field_separator(t)) 101 | .collect(); 102 | 103 | skip_whitespace(&mut *toks); 104 | if !toks.token(']') { 105 | // Record the start and end location of the array in our error. 106 | return Some(Err(ErrorKind::ArrayNotClosed.at(start, toks.location()))); 107 | } 108 | 109 | Some(Ok(values)) 110 | } 111 | 112 | /// Objects begin with {, and then have 0 or more "field":value pairs (for which we just 113 | /// lean on our string and value parsers to handle), and then should close with a }. 114 | /// 115 | /// - `Some(Ok(values))` means we successfully parsed 0 or more object values. 116 | /// - `Some(Err(e))` means that we hit an error parsing the object. 117 | /// - `None` means that this wasn't an object and so nothing was parsed. 118 | fn object(toks: &mut impl Tokens) -> Option, Error>> { 119 | // Note the location of the start of the object. 120 | let start = toks.location(); 121 | 122 | // Try to consume a '{'. If we can't, we consume nothing and bail. 123 | if !toks.token('{') { 124 | return None; 125 | } 126 | skip_whitespace(&mut *toks); 127 | 128 | // Expect object fields like `name: value` to be separated like arrays are. 129 | let values: Result, Error> = toks 130 | .sep_by(|t| object_field(t), |t| field_separator(t)) 131 | .collect(); 132 | 133 | // If we hit any errors above, return it. 134 | let Ok(values) = values else { 135 | return Some(values); 136 | }; 137 | 138 | skip_whitespace(&mut *toks); 139 | if !toks.token('}') { 140 | // Record the start and end location of the object in our error. 141 | return Some(Err(ErrorKind::ObjectNotClosed.at(start, toks.location()))); 142 | } 143 | 144 | Some(Ok(values)) 145 | } 146 | 147 | /// Each object contains zero or more fields, which each have names and values. 148 | /// 149 | /// - `Some(Ok((key, val)))` means we parsed a keyval field pair. 150 | /// - `Some(Err(e))` means we hit some unrecoverable error. 151 | /// - `None` means we parsed nothing and hit the end of the object. 152 | fn object_field(toks: &mut impl Tokens) -> Option> { 153 | if toks.peek() == Some('}') { 154 | return None; 155 | } 156 | let start = toks.location(); 157 | 158 | // Any valid string is also a valid field name. If we don't find a 159 | // string here, or it fails to parse, we kick up a fuss. 160 | let name = match string(&mut *toks) { 161 | None => return Some(Err(ErrorKind::InvalidObjectField.at(start.clone(), start))), 162 | Some(Err(err)) => return Some(Err(err)), 163 | Some(Ok(s)) => s, 164 | }; 165 | 166 | skip_whitespace(&mut *toks); 167 | if !toks.token(':') { 168 | let loc = toks.location(); 169 | return Some(Err( 170 | ErrorKind::MissingObjectFieldSeparator.at(loc.clone(), loc) 171 | )); 172 | } 173 | skip_whitespace(&mut *toks); 174 | 175 | // And after the name comes some arbitrary value: 176 | let val = match value(&mut *toks) { 177 | Ok(val) => val, 178 | Err(e) => return Some(Err(e)), 179 | }; 180 | 181 | Some(Ok((name, val))) 182 | } 183 | 184 | /// Some fairly naive parsing of strings which just manually iterates over tokens 185 | /// to handle basic escapes and pushes them to a string. 186 | /// 187 | /// - `None` if nothing consumed and not a string 188 | /// - `Some(Ok(s))` if we parsed a string successfully 189 | /// - `Some(Err(e))` if something went wrong parsing a string. 190 | fn string(toks: &mut impl Tokens) -> Option> { 191 | // Try to consume a '"'. If we can't, we consume nothing and bail. 192 | if !toks.token('"') { 193 | return None; 194 | } 195 | 196 | // manually iterate over chars and handle them as needed, 197 | // adding them to our string. 198 | let mut s = String::new(); 199 | while let Some(char) = toks.next() { 200 | match char { 201 | // Handle escape chars (naively; ignore \uXXX for instance): 202 | '\\' => { 203 | let Some(escape_char) = toks.next() else { 204 | let loc = toks.location(); 205 | return Some(Err(ErrorKind::UnexpectedEof.at(loc.clone(), loc))); 206 | }; 207 | let substitute_char = match escape_char { 208 | 'n' => '\n', 209 | 't' => '\t', 210 | 'r' => '\r', 211 | '"' => '"', 212 | '\\' => '\\', 213 | // If we don't recognise the escape char, return an error: 214 | c => { 215 | let loc = toks.location(); 216 | return Some(Err(ErrorKind::InvalidEscapeChar(c).at(loc.clone(), loc))); 217 | } 218 | }; 219 | s.push(substitute_char) 220 | } 221 | // String closed; return it! 222 | '"' => return Some(Ok(s)), 223 | // Some standard char; add it to our string. 224 | c => s.push(c), 225 | } 226 | } 227 | 228 | // The string should have been closed above; if we get this far, it hasn't 229 | // been, so return an error. 230 | let loc = toks.location(); 231 | Some(Err(ErrorKind::UnexpectedEof.at(loc.clone(), loc))) 232 | } 233 | 234 | /// true or false; None if neither! 235 | fn bool(toks: &mut impl Tokens) -> Option { 236 | yap::one_of!(toks; 237 | toks.tokens("true".chars()).then_some(true), 238 | toks.tokens("false".chars()).then_some(false) 239 | ) 240 | } 241 | 242 | // Is null seen? None if not. 243 | fn null(toks: &mut impl Tokens) -> bool { 244 | toks.tokens("null".chars()) 245 | } 246 | 247 | /// Use the [`yap::chars::parse_f64`] helper function to parse 248 | /// anything that rust considers a valid float (which is a little more 249 | /// permissive than the JSON standard, actually). 250 | fn number(toks: &mut impl Tokens) -> Option { 251 | yap::chars::parse_f64::(toks) 252 | } 253 | 254 | fn skip_whitespace(toks: &mut impl Tokens) { 255 | toks.skip_while(|c| c.is_ascii_whitespace()); 256 | } 257 | 258 | fn field_separator(toks: &mut impl Tokens) -> bool { 259 | toks.surrounded_by(|t| t.token(','), |t| skip_whitespace(t)) 260 | } 261 | -------------------------------------------------------------------------------- /format.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pathlib 4 | import json 5 | import argparse 6 | 7 | 8 | def main(): 9 | repo_root = pathlib.Path(__name__).parent 10 | runs_root = repo_root / "runs" 11 | default_run_path = sorted(runs_root.glob("*.json"))[-1] 12 | 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument("--run", metavar="PATH", type=pathlib.Path, default=default_run_path, help="Default: %(default)s") 15 | args = parser.parse_args() 16 | 17 | data = json.loads(args.run.read_text()) 18 | cases = sorted(data["libs"].values(), key=lambda c: (c["crate"] if c["crate"] else "", c["name"])) 19 | 20 | print("Name | Overhead (release) | Build (debug) | Parse (release) | Downloads | Version") 21 | print("-----|--------------------|---------------|-----------------|-----------|--------") 22 | for case in cases: 23 | if case["name"] != "null": 24 | count_link = "![Download count](https://img.shields.io/crates/dr/{})".format(case["crate"]) 25 | else: 26 | count_link = "-" 27 | row = [ 28 | case["name"], 29 | fmt_size(case, cases[0]), 30 | fmt_time(case, "build"), 31 | fmt_time(case, "run"), 32 | count_link, 33 | case["version"] if case["version"] else "-", 34 | ] 35 | print(" | ".join(row)) 36 | print() 37 | print(f"*System: {data['os']} {data['os_ver']} ({data['arch']}), {data.get('rustc', '')} w/ `-j {data['cpus']}`*") 38 | 39 | 40 | def fmt_time(case, bench): 41 | bench = case[bench] 42 | if bench is None: 43 | return "N/A" 44 | 45 | value = bench["results"][0]["median"] 46 | if value < 1: 47 | value *= 1000 48 | return "{:.0f}ms".format(value) 49 | else: 50 | return "{:.0f}s".format(value) 51 | 52 | 53 | def fmt_size(case, null_case): 54 | delta = (case["size"] - null_case["size"]) / 1024 55 | return "{:,.0f} KiB".format(delta) 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /runs/2022-07-14-Doomslug.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2022-07-14", 3 | "hostname": "Doomslug", 4 | "os": "Linux", 5 | "os_ver": "5.10.16.3-microsoft-standard-WSL2", 6 | "arch": "x86_64", 7 | "cpus": 20, 8 | "libs": { 9 | "examples/chumsky-app/Cargo.toml": { 10 | "name": "chumsky", 11 | "manifest_path": "examples/chumsky-app/Cargo.toml", 12 | "crate": "ariadne", 13 | "version": "v0.1.5", 14 | "build": { 15 | "results": [ 16 | { 17 | "command": "cargo build -j 20 --package chumsky-app", 18 | "mean": 4.9691443115, 19 | "stddev": 0.04871086987009003, 20 | "median": 4.9503248623, 21 | "user": 7.549100640000001, 22 | "system": 0.8903444, 23 | "min": 4.9250849213, 24 | "max": 5.0498349583, 25 | "times": [ 26 | 4.9503248623, 27 | 4.9440213933, 28 | 4.9250849213, 29 | 4.9764554223, 30 | 5.0498349583 31 | ], 32 | "exit_codes": [ 33 | 0, 34 | 0, 35 | 0, 36 | 0, 37 | 0 38 | ] 39 | } 40 | ] 41 | }, 42 | "run": { 43 | "results": [ 44 | { 45 | "command": "target/release/chumsky-app third_party/nativejson-benchmark/data/canada.json", 46 | "mean": 0.748252019, 47 | "stddev": 0.029624534001256076, 48 | "median": 0.7395108818, 49 | "user": 0.72012992, 50 | "system": 0.028041, 51 | "min": 0.7185949488, 52 | "max": 0.7929958988, 53 | "times": [ 54 | 0.7287642478, 55 | 0.7613941178, 56 | 0.7185949488, 57 | 0.7929958988, 58 | 0.7395108818 59 | ], 60 | "exit_codes": [ 61 | 0, 62 | 0, 63 | 0, 64 | 0, 65 | 0 66 | ] 67 | } 68 | ] 69 | }, 70 | "size": 4760184 71 | }, 72 | "examples/nom-app/Cargo.toml": { 73 | "name": "nom", 74 | "manifest_path": "examples/nom-app/Cargo.toml", 75 | "crate": "nom", 76 | "version": "v7.1.1", 77 | "build": { 78 | "results": [ 79 | { 80 | "command": "cargo build -j 20 --package nom-app", 81 | "mean": 1.6659424982600002, 82 | "stddev": 0.017710873388571382, 83 | "median": 1.6627810414600002, 84 | "user": 2.0015135, 85 | "system": 0.23396246, 86 | "min": 1.64640270246, 87 | "max": 1.69090554646, 88 | "times": [ 89 | 1.69090554646, 90 | 1.65392146546, 91 | 1.64640270246, 92 | 1.6627810414600002, 93 | 1.6757017354600001 94 | ], 95 | "exit_codes": [ 96 | 0, 97 | 0, 98 | 0, 99 | 0, 100 | 0 101 | ] 102 | } 103 | ] 104 | }, 105 | "run": { 106 | "results": [ 107 | { 108 | "command": "target/release/nom-app third_party/nativejson-benchmark/data/canada.json", 109 | "mean": 0.5015574125000001, 110 | "stddev": 0.0079614971225169, 111 | "median": 0.5036880207000001, 112 | "user": 0.47354248, 113 | "system": 0.0279898, 114 | "min": 0.48852677270000006, 115 | "max": 0.5097072677000001, 116 | "times": [ 117 | 0.5036880207000001, 118 | 0.48852677270000006, 119 | 0.5008108497000001, 120 | 0.5097072677000001, 121 | 0.5050541517000001 122 | ], 123 | "exit_codes": [ 124 | 0, 125 | 0, 126 | 0, 127 | 0, 128 | 0 129 | ] 130 | } 131 | ] 132 | }, 133 | "size": 4016288 134 | }, 135 | "examples/null-app/Cargo.toml": { 136 | "name": "null", 137 | "manifest_path": "examples/null-app/Cargo.toml", 138 | "crate": null, 139 | "version": null, 140 | "build": { 141 | "results": [ 142 | { 143 | "command": "cargo build -j 20 --package null-app", 144 | "mean": 0.27607208866, 145 | "stddev": 0.004039183350445615, 146 | "median": 0.27573472666000004, 147 | "user": 0.2277622, 148 | "system": 0.060631439999999995, 149 | "min": 0.26911436266000005, 150 | "max": 0.28102570166, 151 | "times": [ 152 | 0.27489802466, 153 | 0.27864734266, 154 | 0.27561319166000003, 155 | 0.27542462966000003, 156 | 0.27585626166000005, 157 | 0.28102570166, 158 | 0.26911436266000005, 159 | 0.28100385266000005, 160 | 0.27030508966000005, 161 | 0.27883242966000005 162 | ], 163 | "exit_codes": [ 164 | 0, 165 | 0, 166 | 0, 167 | 0, 168 | 0, 169 | 0, 170 | 0, 171 | 0, 172 | 0, 173 | 0 174 | ] 175 | } 176 | ] 177 | }, 178 | "run": { 179 | "results": [ 180 | { 181 | "command": "target/release/null-app third_party/nativejson-benchmark/data/canada.json", 182 | "mean": 0.027776051847692326, 183 | "stddev": 0.0011826406998936877, 184 | "median": 0.02788691854, 185 | "user": 0.027039766153846175, 186 | "system": 0.0007154230769230769, 187 | "min": 0.02388870654, 188 | "max": 0.03026570554, 189 | "times": [ 190 | 0.02841181454, 191 | 0.02789629154, 192 | 0.02747055354, 193 | 0.02514877654, 194 | 0.029796357540000002, 195 | 0.02821384054, 196 | 0.028003253540000002, 197 | 0.02799294254, 198 | 0.02787754554, 199 | 0.02388870654, 200 | 0.02832426754, 201 | 0.029289442540000002, 202 | 0.02823268654, 203 | 0.02910474054, 204 | 0.02927472054, 205 | 0.02700120654, 206 | 0.02674622154, 207 | 0.02874952154, 208 | 0.02705319254, 209 | 0.02635258854, 210 | 0.02679290854, 211 | 0.02800715354, 212 | 0.02934314054, 213 | 0.02898549254, 214 | 0.02978037154, 215 | 0.02715302354, 216 | 0.02865313754, 217 | 0.02789904454, 218 | 0.02708786154, 219 | 0.02748260754, 220 | 0.02845507354, 221 | 0.02790722754, 222 | 0.02905179654, 223 | 0.02587478554, 224 | 0.02939456854, 225 | 0.02760405354, 226 | 0.02806291654, 227 | 0.027213182540000002, 228 | 0.02890886154, 229 | 0.02719116254, 230 | 0.02886045454, 231 | 0.02794811454, 232 | 0.02596825654, 233 | 0.029518000540000002, 234 | 0.02786312954, 235 | 0.027105900540000002, 236 | 0.02723215654, 237 | 0.02864957154, 238 | 0.02749238754, 239 | 0.02717519054, 240 | 0.02956117654, 241 | 0.028249944540000002, 242 | 0.02636851454, 243 | 0.02651949254, 244 | 0.02803657354, 245 | 0.02830593154, 246 | 0.02666412654, 247 | 0.02718427554, 248 | 0.02814344554, 249 | 0.02771619854, 250 | 0.02720653954, 251 | 0.02914724854, 252 | 0.026408775540000002, 253 | 0.02690138354, 254 | 0.02641394954, 255 | 0.02705789754, 256 | 0.02583649754, 257 | 0.02675576454, 258 | 0.02600341554, 259 | 0.02688415954, 260 | 0.027844478540000002, 261 | 0.02661956154, 262 | 0.02761406954, 263 | 0.03026570554, 264 | 0.02535244054, 265 | 0.02845661354, 266 | 0.02587350354, 267 | 0.02871764654, 268 | 0.02888691854, 269 | 0.02834775154, 270 | 0.02730215554, 271 | 0.028458102540000002, 272 | 0.028408363540000002, 273 | 0.02998238954, 274 | 0.025639448540000002, 275 | 0.02857515754, 276 | 0.02814815254, 277 | 0.02849718154, 278 | 0.02942788054, 279 | 0.02808353254, 280 | 0.027281476540000002, 281 | 0.02984013654, 282 | 0.028121774540000002, 283 | 0.02943537454, 284 | 0.02724169954, 285 | 0.02913040654, 286 | 0.02750339054, 287 | 0.02847725654, 288 | 0.02731427154, 289 | 0.02651872454, 290 | 0.02630995454, 291 | 0.02787043254, 292 | 0.02681027554, 293 | 0.02749765054 294 | ], 295 | "exit_codes": [ 296 | 0, 297 | 0, 298 | 0, 299 | 0, 300 | 0, 301 | 0, 302 | 0, 303 | 0, 304 | 0, 305 | 0, 306 | 0, 307 | 0, 308 | 0, 309 | 0, 310 | 0, 311 | 0, 312 | 0, 313 | 0, 314 | 0, 315 | 0, 316 | 0, 317 | 0, 318 | 0, 319 | 0, 320 | 0, 321 | 0, 322 | 0, 323 | 0, 324 | 0, 325 | 0, 326 | 0, 327 | 0, 328 | 0, 329 | 0, 330 | 0, 331 | 0, 332 | 0, 333 | 0, 334 | 0, 335 | 0, 336 | 0, 337 | 0, 338 | 0, 339 | 0, 340 | 0, 341 | 0, 342 | 0, 343 | 0, 344 | 0, 345 | 0, 346 | 0, 347 | 0, 348 | 0, 349 | 0, 350 | 0, 351 | 0, 352 | 0, 353 | 0, 354 | 0, 355 | 0, 356 | 0, 357 | 0, 358 | 0, 359 | 0, 360 | 0, 361 | 0, 362 | 0, 363 | 0, 364 | 0, 365 | 0, 366 | 0, 367 | 0, 368 | 0, 369 | 0, 370 | 0, 371 | 0, 372 | 0, 373 | 0, 374 | 0, 375 | 0, 376 | 0, 377 | 0, 378 | 0, 379 | 0, 380 | 0, 381 | 0, 382 | 0, 383 | 0, 384 | 0, 385 | 0, 386 | 0, 387 | 0, 388 | 0, 389 | 0, 390 | 0, 391 | 0, 392 | 0, 393 | 0, 394 | 0, 395 | 0, 396 | 0, 397 | 0, 398 | 0, 399 | 0 400 | ] 401 | } 402 | ] 403 | }, 404 | "size": 3852288 405 | }, 406 | "examples/pom-app/Cargo.toml": { 407 | "name": "pom", 408 | "manifest_path": "examples/pom-app/Cargo.toml", 409 | "crate": "pom", 410 | "version": "v3.2.0", 411 | "build": { 412 | "results": [ 413 | { 414 | "command": "cargo build -j 20 --package pom-app", 415 | "mean": 0.6009288554600001, 416 | "stddev": 0.011954527435616678, 417 | "median": 0.60320529166, 418 | "user": 0.73326332, 419 | "system": 0.13338152, 420 | "min": 0.5880766846600001, 421 | "max": 0.6140005016600001, 422 | "times": [ 423 | 0.6103415686600001, 424 | 0.5880766846600001, 425 | 0.6140005016600001, 426 | 0.5890202306600001, 427 | 0.60320529166 428 | ], 429 | "exit_codes": [ 430 | 0, 431 | 0, 432 | 0, 433 | 0, 434 | 0 435 | ] 436 | } 437 | ] 438 | }, 439 | "run": { 440 | "results": [ 441 | { 442 | "command": "target/release/pom-app third_party/nativejson-benchmark/data/canada.json", 443 | "mean": 0.8476651460200001, 444 | "stddev": 0.028963132367556748, 445 | "median": 0.8479757276200001, 446 | "user": 0.81363374, 447 | "system": 0.0339904, 448 | "min": 0.8168055026200001, 449 | "max": 0.88193351562, 450 | "times": [ 451 | 0.8168055026200001, 452 | 0.82108909762, 453 | 0.8705218866200001, 454 | 0.8479757276200001, 455 | 0.88193351562 456 | ], 457 | "exit_codes": [ 458 | 0, 459 | 0, 460 | 0, 461 | 0, 462 | 0 463 | ] 464 | } 465 | ] 466 | }, 467 | "size": 4074520 468 | } 469 | } 470 | } -------------------------------------------------------------------------------- /runs/2023-03-23-seon.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2023-03-23", 3 | "hostname": "seon", 4 | "os": "Linux", 5 | "os_ver": "5.4.0-104-generic", 6 | "arch": "x86_64", 7 | "cpus": 8, 8 | "libs": { 9 | "examples/chumsky-app/Cargo.toml": { 10 | "name": "chumsky", 11 | "manifest_path": "examples/chumsky-app/Cargo.toml", 12 | "crate": "ariadne", 13 | "version": "v0.2.0", 14 | "build": { 15 | "results": [ 16 | { 17 | "command": "cargo build -j 8 --package chumsky-app", 18 | "mean": 9.637761924720001, 19 | "stddev": 0.13528939025155165, 20 | "median": 9.67642876212, 21 | "user": 19.03410874, 22 | "system": 1.82739232, 23 | "min": 9.46620146112, 24 | "max": 9.81693457912, 25 | "times": [ 26 | 9.68211702212, 27 | 9.54712779912, 28 | 9.67642876212, 29 | 9.46620146112, 30 | 9.81693457912 31 | ], 32 | "exit_codes": [ 33 | 0, 34 | 0, 35 | 0, 36 | 0, 37 | 0 38 | ] 39 | } 40 | ] 41 | }, 42 | "run": { 43 | "results": [ 44 | { 45 | "command": "target/release/chumsky-app third_party/nativejson-benchmark/data/canada.json", 46 | "mean": 1.83224174468, 47 | "stddev": 0.09950179020276849, 48 | "median": 1.82475635188, 49 | "user": 1.7625470399999998, 50 | "system": 0.06824966, 51 | "min": 1.69851301488, 52 | "max": 1.96292433488, 53 | "times": [ 54 | 1.78991746588, 55 | 1.82475635188, 56 | 1.96292433488, 57 | 1.88509755588, 58 | 1.69851301488 59 | ], 60 | "exit_codes": [ 61 | 0, 62 | 0, 63 | 0, 64 | 0, 65 | 0 66 | ] 67 | } 68 | ] 69 | }, 70 | "size": 5102064 71 | }, 72 | "examples/combine-app/Cargo.toml": { 73 | "name": "combine", 74 | "manifest_path": "examples/combine-app/Cargo.toml", 75 | "crate": "combine", 76 | "version": "v3.8.1", 77 | "build": { 78 | "results": [ 79 | { 80 | "command": "cargo build -j 8 --package combine-app", 81 | "mean": 5.97566948762, 82 | "stddev": 0.06752937905320192, 83 | "median": 5.94078607482, 84 | "user": 9.31636328, 85 | "system": 0.7573197, 86 | "min": 5.91252057282, 87 | "max": 6.04974148182, 88 | "times": [ 89 | 5.94078607482, 90 | 6.04974148182, 91 | 5.91252057282, 92 | 6.04790959582, 93 | 5.92738971282 94 | ], 95 | "exit_codes": [ 96 | 0, 97 | 0, 98 | 0, 99 | 0, 100 | 0 101 | ] 102 | } 103 | ] 104 | }, 105 | "run": { 106 | "results": [ 107 | { 108 | "command": "target/release/combine-app third_party/nativejson-benchmark/data/canada.json", 109 | "mean": 1.1345009392999998, 110 | "stddev": 0.017452506361378857, 111 | "median": 1.1320996025, 112 | "user": 1.0945619199999999, 113 | "system": 0.03992922, 114 | "min": 1.1109305455, 115 | "max": 1.1552027275, 116 | "times": [ 117 | 1.1320996025, 118 | 1.1552027275, 119 | 1.1474939954999999, 120 | 1.1109305455, 121 | 1.1267778255 122 | ], 123 | "exit_codes": [ 124 | 0, 125 | 0, 126 | 0, 127 | 0, 128 | 0 129 | ] 130 | } 131 | ] 132 | }, 133 | "size": 4528352 134 | }, 135 | "examples/nom-app/Cargo.toml": { 136 | "name": "nom", 137 | "manifest_path": "examples/nom-app/Cargo.toml", 138 | "crate": "nom", 139 | "version": "v7.1.3", 140 | "build": { 141 | "results": [ 142 | { 143 | "command": "cargo build -j 8 --package nom-app", 144 | "mean": 3.1123793668, 145 | "stddev": 0.09617417132840148, 146 | "median": 3.0547013880000002, 147 | "user": 4.4482596, 148 | "system": 0.4919105199999999, 149 | "min": 3.0348798820000003, 150 | "max": 3.2597106630000003, 151 | "times": [ 152 | 3.1605454080000004, 153 | 3.0520594930000002, 154 | 3.0547013880000002, 155 | 3.2597106630000003, 156 | 3.0348798820000003 157 | ], 158 | "exit_codes": [ 159 | 0, 160 | 0, 161 | 0, 162 | 0, 163 | 0 164 | ] 165 | } 166 | ] 167 | }, 168 | "run": { 169 | "results": [ 170 | { 171 | "command": "target/release/nom-app third_party/nativejson-benchmark/data/canada.json", 172 | "mean": 1.214101866, 173 | "stddev": 0.08270558269627223, 174 | "median": 1.223305128, 175 | "user": 1.15940982, 176 | "system": 0.05433481999999999, 177 | "min": 1.1066168060000001, 178 | "max": 1.311484689, 179 | "times": [ 180 | 1.2704051680000001, 181 | 1.223305128, 182 | 1.311484689, 183 | 1.158697539, 184 | 1.1066168060000001 185 | ], 186 | "exit_codes": [ 187 | 0, 188 | 0, 189 | 0, 190 | 0, 191 | 0 192 | ] 193 | } 194 | ] 195 | }, 196 | "size": 4457424 197 | }, 198 | "examples/null-app/Cargo.toml": { 199 | "name": "null", 200 | "manifest_path": "examples/null-app/Cargo.toml", 201 | "crate": null, 202 | "version": null, 203 | "build": { 204 | "results": [ 205 | { 206 | "command": "cargo build -j 8 --package null-app", 207 | "mean": 0.4315150056066667, 208 | "stddev": 0.013792526964690608, 209 | "median": 0.42920756194000004, 210 | "user": 0.4146054133333333, 211 | "system": 0.10358707333333332, 212 | "min": 0.41799861844, 213 | "max": 0.45777986744000004, 214 | "times": [ 215 | 0.41799861844, 216 | 0.42935041144, 217 | 0.42327291044000004, 218 | 0.42906471244000005, 219 | 0.45777986744000004, 220 | 0.43162351344000005 221 | ], 222 | "exit_codes": [ 223 | 0, 224 | 0, 225 | 0, 226 | 0, 227 | 0, 228 | 0 229 | ] 230 | } 231 | ] 232 | }, 233 | "run": { 234 | "results": [ 235 | { 236 | "command": "target/release/null-app third_party/nativejson-benchmark/data/canada.json", 237 | "mean": 0.03323835017311476, 238 | "stddev": 0.00289456311360135, 239 | "median": 0.03226416596, 240 | "user": 0.03165806327868853, 241 | "system": 0.00163377868852459, 242 | "min": 0.03095531496, 243 | "max": 0.048197164960000004, 244 | "times": [ 245 | 0.048197164960000004, 246 | 0.03237450596, 247 | 0.03197675696, 248 | 0.03221827496, 249 | 0.03142384396, 250 | 0.03271261996, 251 | 0.03174088196, 252 | 0.03215190596, 253 | 0.03229654396, 254 | 0.03265914496, 255 | 0.03228895396, 256 | 0.03162796396, 257 | 0.031249497959999997, 258 | 0.03209146596, 259 | 0.03177323596, 260 | 0.03162043796, 261 | 0.03315453896, 262 | 0.03259714796, 263 | 0.031737410960000004, 264 | 0.03966217296, 265 | 0.031903106960000004, 266 | 0.032326264960000003, 267 | 0.03186630296, 268 | 0.03197093796, 269 | 0.03289602096, 270 | 0.03200369096, 271 | 0.03226416596, 272 | 0.03198097396, 273 | 0.03184733396, 274 | 0.03222825896, 275 | 0.03225188596, 276 | 0.03169216796, 277 | 0.03223295296, 278 | 0.03209543596, 279 | 0.03269883696, 280 | 0.031862806960000004, 281 | 0.03148831096, 282 | 0.03212150696, 283 | 0.03309940296, 284 | 0.03241580496, 285 | 0.03095531496, 286 | 0.031010279959999998, 287 | 0.03115824196, 288 | 0.03507136896, 289 | 0.03119287296, 290 | 0.03616684296, 291 | 0.03175102296, 292 | 0.03718761396, 293 | 0.03335365596, 294 | 0.034428078960000004, 295 | 0.03370856396, 296 | 0.03268973096, 297 | 0.03375651196, 298 | 0.03296328296, 299 | 0.03596672296, 300 | 0.03329943196, 301 | 0.04374269996, 302 | 0.03534762396, 303 | 0.03485388896, 304 | 0.03419635996, 305 | 0.03593860896 306 | ], 307 | "exit_codes": [ 308 | 0, 309 | 0, 310 | 0, 311 | 0, 312 | 0, 313 | 0, 314 | 0, 315 | 0, 316 | 0, 317 | 0, 318 | 0, 319 | 0, 320 | 0, 321 | 0, 322 | 0, 323 | 0, 324 | 0, 325 | 0, 326 | 0, 327 | 0, 328 | 0, 329 | 0, 330 | 0, 331 | 0, 332 | 0, 333 | 0, 334 | 0, 335 | 0, 336 | 0, 337 | 0, 338 | 0, 339 | 0, 340 | 0, 341 | 0, 342 | 0, 343 | 0, 344 | 0, 345 | 0, 346 | 0, 347 | 0, 348 | 0, 349 | 0, 350 | 0, 351 | 0, 352 | 0, 353 | 0, 354 | 0, 355 | 0, 356 | 0, 357 | 0, 358 | 0, 359 | 0, 360 | 0, 361 | 0, 362 | 0, 363 | 0, 364 | 0, 365 | 0, 366 | 0, 367 | 0, 368 | 0 369 | ] 370 | } 371 | ] 372 | }, 373 | "size": 4295600 374 | }, 375 | "examples/peg-app/Cargo.toml": { 376 | "name": "peg", 377 | "manifest_path": "examples/peg-app/Cargo.toml", 378 | "crate": "peg", 379 | "version": "v0.8.1", 380 | "build": { 381 | "results": [ 382 | { 383 | "command": "cargo build -j 8 --package peg-app", 384 | "mean": 3.17895432094, 385 | "stddev": 0.12563620550396398, 386 | "median": 3.13382325394, 387 | "user": 6.27652494, 388 | "system": 0.7520773, 389 | "min": 3.05711800994, 390 | "max": 3.33086559994, 391 | "times": [ 392 | 3.05711800994, 393 | 3.0790892959400002, 394 | 3.13382325394, 395 | 3.33086559994, 396 | 3.2938754449400003 397 | ], 398 | "exit_codes": [ 399 | 0, 400 | 0, 401 | 0, 402 | 0, 403 | 0 404 | ] 405 | } 406 | ] 407 | }, 408 | "run": { 409 | "results": [ 410 | { 411 | "command": "target/release/peg-app third_party/nativejson-benchmark/data/canada.json", 412 | "mean": 0.027039583685309728, 413 | "stddev": 0.005064854138583792, 414 | "median": 0.025171983880000002, 415 | "user": 0.02541443946902655, 416 | "system": 0.0016297929203539824, 417 | "min": 0.02180774988, 418 | "max": 0.04609679788, 419 | "times": [ 420 | 0.02584863988, 421 | 0.02572559788, 422 | 0.02454185588, 423 | 0.027004253880000002, 424 | 0.02609183488, 425 | 0.02614623788, 426 | 0.02400686388, 427 | 0.02445608588, 428 | 0.02391130488, 429 | 0.02590732788, 430 | 0.025536701880000002, 431 | 0.02433871488, 432 | 0.03593447788, 433 | 0.02631337288, 434 | 0.02319991788, 435 | 0.02396346988, 436 | 0.03341651388, 437 | 0.02590098888, 438 | 0.027508651880000002, 439 | 0.023464343880000002, 440 | 0.03209035288, 441 | 0.029054635880000002, 442 | 0.02328416488, 443 | 0.04190473488, 444 | 0.02325922088, 445 | 0.02323640188, 446 | 0.03174664388, 447 | 0.02536505488, 448 | 0.02554195588, 449 | 0.02406933888, 450 | 0.03196533788, 451 | 0.035434132880000004, 452 | 0.03208344788, 453 | 0.02817761188, 454 | 0.03533774888, 455 | 0.02339624988, 456 | 0.02527344188, 457 | 0.02337668288, 458 | 0.02329374588, 459 | 0.02336158088, 460 | 0.02365827288, 461 | 0.02261797188, 462 | 0.02250410088, 463 | 0.02277093788, 464 | 0.02254110288, 465 | 0.02248211088, 466 | 0.022458284880000002, 467 | 0.02293418388, 468 | 0.02341464388, 469 | 0.02262558688, 470 | 0.03391254588, 471 | 0.02180774988, 472 | 0.02902282788, 473 | 0.02190300288, 474 | 0.04178573688, 475 | 0.02233236988, 476 | 0.02938632788, 477 | 0.02319529788, 478 | 0.022518734880000002, 479 | 0.038432620880000004, 480 | 0.02248409988, 481 | 0.02318445288, 482 | 0.02288580188, 483 | 0.022678043880000002, 484 | 0.02332119988, 485 | 0.02485273088, 486 | 0.02340264588, 487 | 0.02444097188, 488 | 0.02540076088, 489 | 0.02570787688, 490 | 0.02810902288, 491 | 0.023231705880000002, 492 | 0.02307082688, 493 | 0.025171983880000002, 494 | 0.02972645088, 495 | 0.03203647388, 496 | 0.027888516880000002, 497 | 0.04609679788, 498 | 0.02739648588, 499 | 0.02328668088, 500 | 0.02320929788, 501 | 0.023666384880000002, 502 | 0.02999754788, 503 | 0.023119347880000002, 504 | 0.02504687088, 505 | 0.040141452880000004, 506 | 0.02485422088, 507 | 0.025511271880000002, 508 | 0.03483541988, 509 | 0.02704429388, 510 | 0.02581929088, 511 | 0.02484083188, 512 | 0.02598170888, 513 | 0.02507303788, 514 | 0.02470609688, 515 | 0.030529214880000002, 516 | 0.026962859880000002, 517 | 0.02414013788, 518 | 0.02324179788, 519 | 0.02407976288, 520 | 0.02319079488, 521 | 0.034240341880000004, 522 | 0.03201080188, 523 | 0.03526492288, 524 | 0.03056096388, 525 | 0.031544743880000004, 526 | 0.03613170288, 527 | 0.03090282188, 528 | 0.02369576188, 529 | 0.03522426688, 530 | 0.02261503888, 531 | 0.029978895880000002, 532 | 0.03616346588 533 | ], 534 | "exit_codes": [ 535 | 0, 536 | 0, 537 | 0, 538 | 0, 539 | 0, 540 | 0, 541 | 0, 542 | 0, 543 | 0, 544 | 0, 545 | 0, 546 | 0, 547 | 0, 548 | 0, 549 | 0, 550 | 0, 551 | 0, 552 | 0, 553 | 0, 554 | 0, 555 | 0, 556 | 0, 557 | 0, 558 | 0, 559 | 0, 560 | 0, 561 | 0, 562 | 0, 563 | 0, 564 | 0, 565 | 0, 566 | 0, 567 | 0, 568 | 0, 569 | 0, 570 | 0, 571 | 0, 572 | 0, 573 | 0, 574 | 0, 575 | 0, 576 | 0, 577 | 0, 578 | 0, 579 | 0, 580 | 0, 581 | 0, 582 | 0, 583 | 0, 584 | 0, 585 | 0, 586 | 0, 587 | 0, 588 | 0, 589 | 0, 590 | 0, 591 | 0, 592 | 0, 593 | 0, 594 | 0, 595 | 0, 596 | 0, 597 | 0, 598 | 0, 599 | 0, 600 | 0, 601 | 0, 602 | 0, 603 | 0, 604 | 0, 605 | 0, 606 | 0, 607 | 0, 608 | 0, 609 | 0, 610 | 0, 611 | 0, 612 | 0, 613 | 0, 614 | 0, 615 | 0, 616 | 0, 617 | 0, 618 | 0, 619 | 0, 620 | 0, 621 | 0, 622 | 0, 623 | 0, 624 | 0, 625 | 0, 626 | 0, 627 | 0, 628 | 0, 629 | 0, 630 | 0, 631 | 0, 632 | 0, 633 | 0, 634 | 0, 635 | 0, 636 | 0, 637 | 0, 638 | 0, 639 | 0, 640 | 0, 641 | 0, 642 | 0, 643 | 0, 644 | 0, 645 | 0, 646 | 0, 647 | 0 648 | ] 649 | } 650 | ] 651 | }, 652 | "size": 4329776 653 | }, 654 | "examples/pest-app/Cargo.toml": { 655 | "name": "pest", 656 | "manifest_path": "examples/pest-app/Cargo.toml", 657 | "crate": "pest", 658 | "version": "v2.5.6", 659 | "build": { 660 | "results": [ 661 | { 662 | "command": "cargo build -j 8 --package pest-app", 663 | "mean": 6.889660276220001, 664 | "stddev": 0.24463930269502393, 665 | "median": 6.94322656622, 666 | "user": 13.549066499999999, 667 | "system": 1.2859718199999999, 668 | "min": 6.58077237122, 669 | "max": 7.195389754220001, 670 | "times": [ 671 | 6.712187703220001, 672 | 6.58077237122, 673 | 6.94322656622, 674 | 7.01672498622, 675 | 7.195389754220001 676 | ], 677 | "exit_codes": [ 678 | 0, 679 | 0, 680 | 0, 681 | 0, 682 | 0 683 | ] 684 | } 685 | ] 686 | }, 687 | "run": { 688 | "results": [ 689 | { 690 | "command": "target/release/pest-app third_party/nativejson-benchmark/data/canada.json", 691 | "mean": 1.13168286254, 692 | "stddev": 0.039462177838940835, 693 | "median": 1.1333543727400002, 694 | "user": 1.05622184, 695 | "system": 0.07522968, 696 | "min": 1.08005490174, 697 | "max": 1.1776397577400002, 698 | "times": [ 699 | 1.1605227147400001, 700 | 1.1333543727400002, 701 | 1.10684256574, 702 | 1.08005490174, 703 | 1.1776397577400002 704 | ], 705 | "exit_codes": [ 706 | 0, 707 | 0, 708 | 0, 709 | 0, 710 | 0 711 | ] 712 | } 713 | ] 714 | }, 715 | "size": 4446688 716 | }, 717 | "examples/pom-app/Cargo.toml": { 718 | "name": "pom", 719 | "manifest_path": "examples/pom-app/Cargo.toml", 720 | "crate": "pom", 721 | "version": "v3.2.0", 722 | "build": { 723 | "results": [ 724 | { 725 | "command": "cargo build -j 8 --package pom-app", 726 | "mean": 1.17102681318, 727 | "stddev": 0.012937062919207227, 728 | "median": 1.17500261518, 729 | "user": 1.86460048, 730 | "system": 0.27456344, 731 | "min": 1.1568075521799999, 732 | "max": 1.18540887618, 733 | "times": [ 734 | 1.15809316018, 735 | 1.17982186218, 736 | 1.18540887618, 737 | 1.17500261518, 738 | 1.1568075521799999 739 | ], 740 | "exit_codes": [ 741 | 0, 742 | 0, 743 | 0, 744 | 0, 745 | 0 746 | ] 747 | } 748 | ] 749 | }, 750 | "run": { 751 | "results": [ 752 | { 753 | "command": "target/release/pom-app third_party/nativejson-benchmark/data/canada.json", 754 | "mean": 2.14320024194, 755 | "stddev": 0.04284806353505112, 756 | "median": 2.13728204654, 757 | "user": 2.0889882199999996, 758 | "system": 0.05367553999999999, 759 | "min": 2.1085660345400004, 760 | "max": 2.21504690154, 761 | "times": [ 762 | 2.11242590154, 763 | 2.14268032554, 764 | 2.21504690154, 765 | 2.1085660345400004, 766 | 2.13728204654 767 | ], 768 | "exit_codes": [ 769 | 0, 770 | 0, 771 | 0, 772 | 0, 773 | 0 774 | ] 775 | } 776 | ] 777 | }, 778 | "size": 4516072 779 | }, 780 | "examples/winnow-app/Cargo.toml": { 781 | "name": "winnow", 782 | "manifest_path": "examples/winnow-app/Cargo.toml", 783 | "crate": "winnow", 784 | "version": "v0.4.0", 785 | "build": { 786 | "results": [ 787 | { 788 | "command": "cargo build -j 8 --package winnow-app", 789 | "mean": 2.2363113001, 790 | "stddev": 0.030415015085390396, 791 | "median": 2.2475373565, 792 | "user": 2.7787813800000003, 793 | "system": 0.29403672, 794 | "min": 2.1867445485, 795 | "max": 2.2639143625, 796 | "times": [ 797 | 2.2294770855, 798 | 2.1867445485, 799 | 2.2538831475, 800 | 2.2639143625, 801 | 2.2475373565 802 | ], 803 | "exit_codes": [ 804 | 0, 805 | 0, 806 | 0, 807 | 0, 808 | 0 809 | ] 810 | } 811 | ] 812 | }, 813 | "run": { 814 | "results": [ 815 | { 816 | "command": "target/release/winnow-app third_party/nativejson-benchmark/data/canada.json", 817 | "mean": 0.9888506441, 818 | "stddev": 0.04442709119907814, 819 | "median": 0.9837690201, 820 | "user": 0.93516116, 821 | "system": 0.05295166, 822 | "min": 0.9418596051000001, 823 | "max": 1.0618351921, 824 | "times": [ 825 | 0.9707891981000001, 826 | 0.9860002051000001, 827 | 0.9837690201, 828 | 1.0618351921, 829 | 0.9418596051000001 830 | ], 831 | "exit_codes": [ 832 | 0, 833 | 0, 834 | 0, 835 | 0, 836 | 0 837 | ] 838 | } 839 | ] 840 | }, 841 | "size": 4437744 842 | }, 843 | "examples/yap-app/Cargo.toml": { 844 | "name": "yap", 845 | "manifest_path": "examples/yap-app/Cargo.toml", 846 | "crate": "yap", 847 | "version": "v0.10.0", 848 | "build": { 849 | "results": [ 850 | { 851 | "command": "cargo build -j 8 --package yap-app", 852 | "mean": 0.8371240685, 853 | "stddev": 0.02464052521204184, 854 | "median": 0.8492245831, 855 | "user": 1.1050418999999998, 856 | "system": 0.18283754000000002, 857 | "min": 0.7982224431, 858 | "max": 0.8593426981000001, 859 | "times": [ 860 | 0.8279110501, 861 | 0.8492245831, 862 | 0.8593426981000001, 863 | 0.8509195681, 864 | 0.7982224431 865 | ], 866 | "exit_codes": [ 867 | 0, 868 | 0, 869 | 0, 870 | 0, 871 | 0 872 | ] 873 | } 874 | ] 875 | }, 876 | "run": { 877 | "results": [ 878 | { 879 | "command": "target/release/yap-app third_party/nativejson-benchmark/data/canada.json", 880 | "mean": 0.9717407231399999, 881 | "stddev": 0.018986260793857825, 882 | "median": 0.97739544194, 883 | "user": 0.91613052, 884 | "system": 0.055273779999999995, 885 | "min": 0.9391895449400001, 886 | "max": 0.98861600094, 887 | "times": [ 888 | 0.9795235249400001, 889 | 0.97739544194, 890 | 0.98861600094, 891 | 0.97397910294, 892 | 0.9391895449400001 893 | ], 894 | "exit_codes": [ 895 | 0, 896 | 0, 897 | 0, 898 | 0, 899 | 0 900 | ] 901 | } 902 | ] 903 | }, 904 | "size": 4386400 905 | } 906 | } 907 | } -------------------------------------------------------------------------------- /third_party/nativejson-benchmark/LINK: -------------------------------------------------------------------------------- 1 | https://github.com/miloyip/nativejson-benchmark.git 2 | --------------------------------------------------------------------------------