# kevinmehall/rust-peg: A PEG Parser Generator for Rust

> rust-peg turns a concise Parsing Expression Grammar into a recursive descent Rust parser through a proc macro. It suits Rust developers who want grammar-local actions, parameterized rules, precedence climbing, and flexible input types without building a parser from parser combinators by hand.

**kevinmehall/rust-peg** — Parsing Expression Grammar (PEG) parser generator for Rust

- Repository: https://github.com/kevinmehall/rust-peg
- Website: https://crates.io/crates/peg
- Stars: 1,604 · Forks: 118
- Language: Rust
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/kevinmehall-rust-peg

## Why rust-peg fits grammar-driven Rust parsers

rust-peg addresses the case where a Rust application needs a parser described as syntax rules rather than as a sequence of manual parsing calls. The grammar is written inside a Rust macro invocation, and the macro builds a recursive descent parser from that definition. This is a different organization model from a conventional parser library in which the application constructs parsing functions or combinators in source code.

The project is aimed at developers who can express the language as a Parsing Expression Grammar, or PEG. The README specifically lists input from `&str`, `&[u8]`, `&[T]`, and custom types implementing the required traits. It also supports custom parse-error reporting, rule arguments, precedence climbing for prefix, postfix, and infix expressions, and rule-level tracing. These features make the crate relevant to configuration formats, small domain-specific languages, command-line syntaxes, and other grammars that benefit from keeping parsing rules close to their transformation code.

The central trade-off is the parser model. The README comparison lists PEG parsing, action code inside the grammar, and proc-macro integration. It does not list streaming input support. That boundary matters when input must be consumed incrementally from a network stream or a large source. The crate is better suited to a value already available to the parser than to a pipeline whose data arrives in pieces.

## How the grammar macro becomes a recursive descent parser

The `peg::parser!` macro is the central integration point. A grammar declaration names the generated parser and input type, then defines rules and Rust action blocks. For example, the README defines `list_parser()` for `str`, captures a bracketed sequence of numbers, and returns a `Vec<u32>`. The generated rule parses each numeric match with a `u32` conversion and returns the collected values.

The example shows the data flow directly. The `number()` rule captures one or more characters from `'0'` through `'9'` with `$(['0'..='9']+)`. Its action calls `n.parse()` and converts a failure into `Err("u32")`. The public `list()` rule then matches `[`, applies `number() ** ","` to parse a comma-separated sequence, matches `]`, and returns the resulting vector. Parsing structure and conversion code remain adjacent, which makes the grammar readable as executable syntax documentation.

The project is implemented as a workspace containing `peg-macros`, `peg-runtime`, and `xtask`. The runtime supplies parser support, while the macro package processes the grammar and Rust action code. The README says that grammar errors can produce helpful `rustc` messages. It also documents rule arguments, which allow rules to become reusable templates, and precedence climbing, which is useful for expression grammars. These features are compiler-time integration choices, not runtime configuration switches.

## Installing rust-peg and parsing the first bracketed number list

The README does not provide a separate installation command. The repository manifest identifies the published package as `peg` and records its current workspace version as `0.8.6`. The manifest also states that the workspace requires Rust 1.82.0. A Rust project can use the crate through its normal Cargo dependency configuration, but the README does not prescribe a particular `cargo add` command or a version-selection policy, so those details should come from the project's current Cargo documentation.

The first complete use is the README's `list_parser` example. It declares the parser with `peg::parser!`, defines the input as `str`, and exposes `list()` as the callable rule. The following code is the documented example:

```rust
peg::parser!{
  grammar list_parser() for str {
    rule number() -> u32
      = n:$(['0'..='9']+) {? n.parse().or(Err("u32")) }

    pub rule list() -> Vec<u32>
      = "[" l:(number() ** ",") "]" { l }
  }
}
```

A surrounding Rust entry point calls the generated `list_parser::list` function with a string such as `"[1,1,2,3,5,8]"`. The README's expected result is `Ok(vec![1, 1, 2, 3, 5, 8])`. The action converts the captured digits and returns the collected list, so this example verifies both grammar parsing and the value transformation performed after recognition.

## Input flexibility, precedence, and parser diagnostics

The input side is broader than the README's string example suggests. The project lists `&str`, `&[u8]`, `&[T]`, and custom types implementing traits. That makes a single grammar style applicable to text, byte-oriented formats, and token or element sequences, although the README does not document the complete trait set or all adapter details. A project using a custom input type should consult the rustdoc rather than assume that every arbitrary type is accepted.

Expression parsing has dedicated support. The README specifically names precedence climbing for prefix, postfix, and infix expressions. This is more targeted than a general description of operator support, and it is likely the relevant feature for parsers that must distinguish multiplication from addition or handle unary and postfix forms. The example does not show a precedence grammar, so the exact rule syntax must come from the grammar reference in the linked rustdoc.

Error handling is also configurable according to the feature list. The crate can report parse errors in a customized form, while the macro is intended to surface grammar-definition and embedded-Rust errors through useful `rustc` diagnostics. Rule-level tracing is available as a debugging aid, and the `trace` feature is declared in `Cargo.toml`. The README says to add `--features trace` when tracing tests, so this is a documented development and debugging path rather than an invented user workflow.

## The limits of rust-peg compared with nom, pest, and lalrpop

The repository's comparison table gives a direct basis for deciding whether the parser model fits the application. rust-peg uses PEG semantics, places action code in the grammar, integrates through a proc macro in a block, supports precedence climbing, supports parameterized rules, and does not provide streaming input.

`nom` takes a different approach. The table classifies it as a combinator-based parser library, with action code in source, `&[u8]` and custom input support, parameterized rules, and streaming input. A developer choosing `nom` would assemble parsing behavior from library functions instead of defining the whole grammar in a `peg::parser!` declaration. The table does not list precedence climbing for `nom`.

`pest` is also PEG-based, but the table describes its integration as a proc macro using a file and its input as `&str`. It places action code externally and does not list parameterized rules. `lalrpop` uses LR(1), integrates through a build script, accepts `&str`, and supports parameterized rules according to the table, but it does not use PEG semantics and does not list precedence climbing. These are architectural differences, not claims about parser speed or error quality. rust-peg is the clearest fit when grammar-local actions and direct Rust macro integration are priorities.

## Maintenance, licensing, and the cost of changing a grammar

The repository is not archived, and its last push was on 2026-05-04. The release list includes `0.8.6` on 2026-05-04, `0.8.5` on 2025-03-02, and `0.8.4` on 2024-07-21. The workspace manifest declares the MIT license and package version `0.8.6`. The license permits use under its terms, but this review does not provide legal advice and does not assess obligations outside the repository's stated license.

Changing a grammar is primarily a source-code maintenance task because the grammar and action blocks are compiled through the macro. The repository includes separate run-pass and compile-fail test directories, with `trybuild` used to test functionality and error messages for incorrect grammars. The README says the entire suite can be run with `cargo test`; it also documents a targeted command for one file. Those tests are relevant after changing rule syntax, embedded Rust actions, or macro diagnostics.

The compile-fail tests are intentionally tied to the minimum supported Rust version because `rustc` error messages can change. This creates a specific maintenance constraint: a newer compiler may alter diagnostics even when the grammar's behavior has not changed. The workspace declares `rust-version = "1.82.0"`, so that version is the first compatibility fact to check when reproducing the documented test behavior. The project also has an `unstable` feature and a `trace` feature, but the README does not describe the unstable API, so its adoption cost cannot be inferred from the README.

## Conclusion

Adopt rust-peg when the grammar belongs directly in a Rust parser and input is primarily in memory, especially if precedence handling or parameterized rules matter. Do not choose it for streaming parsers or when an LR parser is required. Check first that your input type fits the supported parser model, that Rust 1.82.0 is acceptable, and that the MIT-licensed dependency and its proc-macro integration fit the application.

## FAQ

### What input types does rust-peg support?

The README lists `&str`, `&[u8]`, `&[T]`, and custom types implementing the required traits. It does not document the complete trait requirements in the repository summary.

### Does rust-peg support streaming input?

No. The README comparison table marks streaming input as unsupported for `peg`, while it marks `nom` as supporting streaming input.

### How do I create a parser with rust-peg?

Use the `peg::parser!` macro, declare a grammar and input type, define rules, and add Rust action code that returns the parsed value. The README provides `list_parser()` as a complete example.

## Sources

- [kevinmehall/rust-peg on GitHub](https://github.com/kevinmehall/rust-peg)
- [License: MIT](https://github.com/kevinmehall/rust-peg/blob/master/LICENSE)
- [Project website](https://crates.io/crates/peg)
- [README](https://github.com/kevinmehall/rust-peg/blob/master/README.md)
- [Releases](https://github.com/kevinmehall/rust-peg/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kevinmehall-rust-peg
