Skip to main content

parser_lang/
lib.rs

1//! # parser_lang
2//!
3//! The parsing toolkit for the `-lang` family: a token cursor a hand-written
4//! recursive-descent grammar threads, a Pratt precedence engine for expressions,
5//! and error recovery that emits source-annotated diagnostics. It owns no grammar
6//! and no AST — the grammar's own functions build whatever output they like — so
7//! one toolkit serves every language built on it.
8//!
9//! ## The pieces
10//!
11//! - [`Parser`] — a cursor over a [`token_lang`] `&[Token<K>]`. It skips trivia,
12//!   stops cleanly at the end of input, matches kinds by predicate (so a kind that
13//!   carries data needs no `PartialEq`), and records [`diag_lang`] diagnostics for
14//!   recoverable errors so one run reports many. `expect`, `recover`, `checkpoint`,
15//!   and the `repeated` / `separated` helpers cover the common shapes.
16//! - [`Pratt`] — a precedence-climbing expression engine: describe prefix operands,
17//!   infix binding powers, and how to combine them, and the driver handles
18//!   precedence and associativity.
19//!
20//! The grammar consumes tokens from [`token_lang`] and reports errors as
21//! [`diag_lang`] [`Diagnostic`]s; what it *produces* is yours — an `ast-lang` tree,
22//! a value, anything.
23//!
24//! ## Quickstart
25//!
26//! A calculator that evaluates `1 + 2 * 3` to `7` with correct precedence:
27//!
28//! ```
29//! use parser_lang::{Parser, Pratt, Span, Token, TokenKind};
30//!
31//! #[derive(Clone, Copy)]
32//! enum K { Num(i64), Plus, Star, Eof }
33//! impl TokenKind for K {
34//!     fn is_eof(&self) -> bool { matches!(self, K::Eof) }
35//! }
36//!
37//! struct Calc;
38//! impl<'t> Pratt<'t, K> for Calc {
39//!     type Output = i64;
40//!     fn prefix(&mut self, p: &mut Parser<'t, K>) -> Option<i64> {
41//!         match p.bump()?.kind() {
42//!             K::Num(n) => Some(*n),
43//!             _ => None,
44//!         }
45//!     }
46//!     fn infix_binding(&self, k: &K) -> Option<(u8, u8)> {
47//!         match k {
48//!             K::Plus => Some((1, 2)),
49//!             K::Star => Some((3, 4)),
50//!             _ => None,
51//!         }
52//!     }
53//!     fn infix(&mut self, op: &'t Token<K>, l: i64, r: i64) -> Option<i64> {
54//!         match op.kind() {
55//!             K::Plus => Some(l + r),
56//!             K::Star => Some(l * r),
57//!             _ => None,
58//!         }
59//!     }
60//! }
61//!
62//! let s = |i| Span::new(i, i + 1);
63//! let tokens = [
64//!     Token::new(K::Num(1), s(0)),
65//!     Token::new(K::Plus, s(1)),
66//!     Token::new(K::Num(2), s(2)),
67//!     Token::new(K::Star, s(3)),
68//!     Token::new(K::Num(3), s(4)),
69//!     Token::new(K::Eof, Span::empty(5)),
70//! ];
71//! let mut p = Parser::new(&tokens);
72//! assert_eq!(Calc.parse(&mut p), Some(7));
73//! assert!(!p.has_errors());
74//! ```
75//!
76//! ## Features
77//!
78//! - `std` (default) — the standard library; without it the crate is `no_std`
79//!   (it always needs `alloc`). Forwards to `token-lang/std` and `diag-lang/std`.
80//!
81//! ## Stability
82//!
83//! The public surface is being designed across the 0.x series and freezes at
84//! `1.0.0`, after which it follows Semantic Versioning: no breaking changes before
85//! `2.0`, additions arrive in minor releases, and the MSRV (Rust 1.85) only rises
86//! in a minor. The frozen surface is catalogued in
87//! [`docs/API.md`](https://github.com/jamesgober/parser-lang/blob/main/docs/API.md).
88
89#![cfg_attr(not(feature = "std"), no_std)]
90#![cfg_attr(docsrs, feature(doc_cfg))]
91#![deny(missing_docs)]
92#![forbid(unsafe_code)]
93
94extern crate alloc;
95
96mod parser;
97mod pratt;
98
99pub use parser::{Checkpoint, Parser};
100pub use pratt::Pratt;
101
102// Re-exported so a grammar can name the token and error types this crate's API is
103// built on without also having to depend on `token-lang` and `diag-lang` directly.
104pub use diag_lang::Diagnostic;
105pub use token_lang::{Span, Token, TokenKind};