parser-lang 1.0.0

Recursive-descent + Pratt parser infrastructure with error recovery.
Documentation
# parser-lang — API Reference

> Complete reference for every public item in `parser-lang`, with examples.
> **Status: stable as of `1.0.0`.** The surface below is frozen under Semantic Versioning — no breaking changes before `2.0`.

## Table of Contents

- [Overview]#overview
- [Installation]#installation
- [`Parser`]#parser
- [`Checkpoint`]#checkpoint
- [`Pratt`]#pratt
- [Re-exports]#re-exports
- [Feature flags]#feature-flags
- [Frozen surface]#frozen-surface

---

## Overview

parser-lang is the parsing toolkit of the `-lang` family: a token cursor a
hand-written recursive-descent grammar threads, a Pratt precedence engine for
expressions, and error recovery that records source-annotated diagnostics. It owns
no grammar and no AST — the grammar's own functions build whatever output they
like — so one toolkit serves every language.

Input is a `&[Token<K>]` from [`token-lang`](https://docs.rs/token-lang); errors
are [`diag-lang`](https://docs.rs/diag-lang) `Diagnostic`s. Kinds are matched by
predicate (`|k| matches!(k, Kind::Plus)`), so a kind that carries data — an
interned identifier, a literal — works without a `PartialEq` bound.

---

## Installation

```toml
[dependencies]
parser-lang = "1"
```

---

## `Parser`

`Parser<'t, K>` is the cursor a recursive-descent grammar drives. It borrows the
token stream, skips trivia automatically, stops cleanly at the end of input, and
collects diagnostics so one parse reports many errors.

```rust
use parser_lang::{Parser, Span, Token, TokenKind};

#[derive(Clone, Copy)]
enum K { Num, Plus, Eof }
impl TokenKind for K { fn is_eof(&self) -> bool { matches!(self, K::Eof) } }

let tokens = [
    Token::new(K::Num, Span::new(0, 1)),
    Token::new(K::Plus, Span::new(2, 3)),
    Token::new(K::Num, Span::new(4, 5)),
    Token::new(K::Eof, Span::empty(5)),
];
let mut p = Parser::new(&tokens);
assert!(p.at(|k| matches!(k, K::Num)));
p.bump();
assert!(p.eat(|k| matches!(k, K::Plus)).is_some());
```

| Method | Description |
|--------|-------------|
| `new(tokens: &'t [Token<K>]) -> Parser<'t, K>` | A cursor over `tokens`, at the first significant token. |
| `peek(&self) -> Option<&'t Token<K>>` | The current significant token, or `None` at the end. |
| `peek_kind(&self) -> Option<&'t K>` | The current token's kind. |
| `span(&self) -> Span` | The current token's span, or an empty end-of-input span. |
| `at_end(&self) -> bool` | `true` at end of input or on the eof marker. |
| `at(&self, pred) -> bool` | Whether the current kind satisfies `pred`. |
| `bump(&mut self) -> Option<&'t Token<K>>` | Consume and return the current token. |
| `eat(&mut self, pred) -> Option<&'t Token<K>>` | Consume the current token if it matches `pred`. |
| `expect(&mut self, pred, description: &str) -> Option<&'t Token<K>>` | Consume if it matches, else record `expected {description}`. |
| `error(&mut self, message)` / `error_at(&mut self, span, message)` | Record an error diagnostic. |
| `recover(&mut self, sync)` | Skip to a synchronizing token (or the end) for recovery. |
| `repeated(&mut self, parse) -> Vec<T>` | Parse items until `parse` returns `None`. |
| `separated(&mut self, sep, parse) -> Vec<T>` | Parse a `sep`-separated list. |
| `checkpoint(&self) -> Checkpoint` / `rewind(&mut self, cp)` | Save and restore the cursor and error log, for speculative parsing. |
| `errors(&self) -> &[Diagnostic]` / `has_errors(&self) -> bool` / `into_errors(self) -> Vec<Diagnostic>` | Inspect or take the recorded diagnostics. |

The predicate methods take `impl FnOnce(&K) -> bool`; `recover`/`separated` take
`Fn`/`FnMut`. The token references returned are tied to the stream's lifetime
`'t`, so they outlive the borrow of the parser.

## `Checkpoint`

An opaque, `Copy` snapshot of the cursor position and error count, from
`Parser::checkpoint`. Pass it to `Parser::rewind` to undo a speculative parse —
the cursor returns and any diagnostics recorded since are dropped.

---

## `Pratt`

A precedence-climbing expression grammar, driven over a `Parser`. Implement three
methods; the provided `expression` / `parse` driver handles precedence and
associativity.

| Method | Description |
|--------|-------------|
| `type Output` | What the grammar builds (a node, a value, …). |
| `prefix(&mut self, p) -> Option<Output>` | Parse an operand: literal, parenthesized group, prefix-unary, or a postfix chain. |
| `infix_binding(&self, kind: &K) -> Option<(u8, u8)>` | The `(left, right)` binding power of an infix operator, or `None`. Left below right is left-associative; above is right-associative. |
| `infix(&mut self, op, left, right) -> Option<Output>` | Combine operands with the operator. |
| `expression(&mut self, p, min_bp) -> Option<Output>` _(provided)_ | The precedence-climbing driver. |
| `parse(&mut self, p) -> Option<Output>` _(provided)_ | Parse a full expression (minimum binding power 0). |

Returning `None` from any method signals a recoverable error (after recording a
diagnostic on the parser), and the driver unwinds. Postfix operators (a call `()`,
an index `[]`) are handled inside `prefix` by looping on the trailing tokens after
the atom.

```rust
use parser_lang::{Parser, Pratt, Span, Token, TokenKind};

#[derive(Clone, Copy)]
enum K { Num(i64), Plus, Star, Eof }
impl TokenKind for K { fn is_eof(&self) -> bool { matches!(self, K::Eof) } }

struct Calc;
impl<'t> Pratt<'t, K> for Calc {
    type Output = i64;
    fn prefix(&mut self, p: &mut Parser<'t, K>) -> Option<i64> {
        match p.bump()?.kind() { K::Num(n) => Some(*n), _ => None }
    }
    fn infix_binding(&self, k: &K) -> Option<(u8, u8)> {
        match k { K::Plus => Some((1, 2)), K::Star => Some((3, 4)), _ => None }
    }
    fn infix(&mut self, op: &'t Token<K>, l: i64, r: i64) -> Option<i64> {
        match op.kind() { K::Plus => Some(l + r), K::Star => Some(l * r), _ => None }
    }
}
```

---

## Re-exports

The token and error types this crate's API is built on are re-exported, so a
grammar need not also name `token-lang` and `diag-lang`:

- `Token`, `TokenKind`, `Span` — from [`token-lang`]https://docs.rs/token-lang; the input stream.
- `Diagnostic` — from [`diag-lang`]https://docs.rs/diag-lang; the recorded errors.

---

## Feature flags

| Feature | Default | Description |
|---------|---------|-------------|
| `std` | yes | Standard library. With it off, the crate is `no_std` (it always needs `alloc`). Forwards to `token-lang/std` and `diag-lang/std`. |

---

## Frozen surface

The items above are the complete public API, frozen as of `1.0.0`, under Semantic
Versioning: no breaking change before `2.0`, additions in minors, MSRV (Rust 1.85)
rising only in a minor.

Deliberately left out of 1.0, each addable later without a breaking change:

- **No `ast-lang` dependency.** A parser's output is generic — the grammar's
  actions build whatever they like (an `ast-lang` tree, a value), so parser-lang
  needs only the token and diagnostic crates. The master plan lists `ast` as a
  dependency; it is a use-site relationship, not an import. See `dev/NOTES.md`.
- **No combinator trait / monadic layer.** parser-lang is a cursor-and-helpers
  toolkit for hand-written recursive descent (with `repeated` / `separated`), not a
  combinator framework; a combinator layer could be added on top.
- **No built-in expression-depth limit.** Expression nesting is bounded by the call
  stack, as in any recursive-descent parser; a configurable depth guard can be
  added later.

---

<sub>Copyright &copy; 2026 <strong>James Gober</strong>.</sub>