Skip to main content

abnf_oracle/
lib.rs

1//! ABNF grammars as a testing oracle.
2//!
3//! Parses ABNF grammars (RFC 5234 as amended by RFC 7405 and Errata 2968 and 3076), decides
4//! whether an input string is in the language of a rule, and generates strings that are.
5//!
6//! The emphasis is on being *right*, not fast. Alternation is unordered, backtracking is full,
7//! ambiguity is tolerated, and the recognizer computes the set of all end positions an element
8//! can reach rather than committing to the first branch that matches. What this crate is
9//! deliberately **not** is listed in `SCOPE.md` 3: no parse trees, no ambiguity detection, no
10//! PEG or regex semantics, no byte-oriented matching, and no performance promises.
11//!
12//! # Shape of the API
13//!
14//! Three types, used in order:
15//!
16//! 1. [`Grammar`] — the syntactic layer. What the parser produces: definitions as written.
17//! 2. `CheckedGrammar` — the semantic layer, produced by `Grammar::check`. Incremental
18//!    alternatives are merged, names are resolved, and the analyses the recognizer and
19//!    generator rely on have been run. It is the only route to either of them, so there is no
20//!    way to recognize against a grammar whose structure was never validated.
21//! 3. `Recognizer` / `Generator` — bound to a checked grammar and a start rule.
22//!
23//! # Example
24//!
25//! ```
26//! use abnf_oracle::{Generator, Grammar, Recognizer};
27//!
28//! // A grammar, as it would appear in an RFC.
29//! let source = concat!(
30//!     "full-date     = date-fullyear \"-\" date-month \"-\" date-mday\r\n",
31//!     "date-fullyear = 4DIGIT\r\n",
32//!     "date-month    = 2DIGIT\r\n",
33//!     "date-mday     = 2DIGIT\r\n",
34//! );
35//!
36//! // Parse, then check. `check` merges incremental definitions, resolves every name and runs
37//! // the analyses; it is the only way to reach a recognizer or a generator.
38//! let grammar = Grammar::parse(source)
39//!     .expect("valid ABNF")
40//!     .check()
41//!     .expect("no structural errors");
42//!
43//! // Recognize.
44//! assert!(Recognizer::new(&grammar, "2026-09-20").accepts("full-date").unwrap());
45//! assert!(!Recognizer::new(&grammar, "20260920").accepts("full-date").unwrap());
46//!
47//! // Generate. The same seed always gives the same string.
48//! let mut generator = Generator::new(&grammar, 42);
49//! let produced = generator.generate("full-date").expect("generates");
50//!
51//! // What the generator produces, the recognizer accepts.
52//! assert!(Recognizer::new(&grammar, &produced).accepts("full-date").unwrap());
53//! ```
54//!
55//! # Errors are not rejections
56//!
57//! A limit, a prose value or an unrepresentable terminal makes a question *unanswerable*, which
58//! is different from answering "no". Every such case is an `Err`, never `Ok(false)`, so a
59//! caller can never mistake "could not decide" for "does not match".
60
61#![forbid(unsafe_code)]
62#![deny(missing_docs)]
63
64mod ast;
65mod check;
66mod core_rules;
67mod display;
68mod error;
69mod generate;
70mod lint;
71mod parse;
72mod recognize;
73mod rng;
74
75pub use ast::{
76    CharVal, DefinedAs, Definition, Element, Grammar, Ignored, MinLen, Node, NodeId, NumVal,
77    ParseOptions, Repeat, Rule, RuleId, RuleName, Span, Witness,
78};
79pub use check::CheckedGrammar;
80pub use error::{CheckError, CheckErrors, GenError, LintWarning, MatchError, ParseError};
81pub use generate::{
82    DEFAULT_GEN_DEPTH, DEFAULT_MAX_OUTPUT_LEN, DEFAULT_MAX_STEPS, DEFAULT_SPREAD, GenOptions,
83    Generator,
84};
85pub use recognize::{DEFAULT_MAX_DEPTH, MatchOptions, Recognizer};