Skip to main content

praxis_input_parser/
lib.rs

1//! The unified input parser DSL (§7, §14.1).
2//!
3//! Responsibility (per the design): the parser-expression typed AST, the backtick
4//! template scanner, static validation, compile-time result-type synthesis, and
5//! parser-plan construction. The DSL has its own typed AST (§7.9) and is **not**
6//! lowered immediately into string-splitting calls.
7//!
8//! The crate covers the atomics ([`AtomicKind::ALL`] — `int`, `uint`, `float`,
9//! `byte`, `char`, `digit`, `word`, `identifier`, `text`, `rest`), the
10//! constructors ([`Constructor::ALL`] — `lines`, `sections`, `csv`, `ws`,
11//! `sep`, `grid`, `matrix`, `chars`, `one_of`, `block`, `choice`, `optional`,
12//! `scan`, `repeated`), backtick templates, type synthesis, and plan lowering.
13//! The runtime interpreter lives in `praxis-runtime::parser`.
14
15pub mod ast;
16pub mod body;
17pub mod call;
18pub mod plan;
19pub mod scan;
20pub mod synthesize;
21pub mod validate;
22
23pub use ast::{
24    ArgShape, AtomicKind, BlockItem, CaptureName, Constructor, EmptySeparator, InvalidCaptureName,
25    InvalidRepeatCount, ParserAst, RepeatCount, SectionItem, Separator, SkipPolicy, TemplatePart,
26    WsPolicy,
27};
28pub use call::{CallArg, build_call, build_repeated_tail};
29pub use plan::{
30    BlockItemNode, CompiledPlan, FieldOrder, MAX_PLANS, ParserPlan, PlanId, PlanNode,
31    SectionItemNode, SourceOrder, TemplatePartNode, TemplateShape, TooManyPlans, get_plan,
32    lower_to_plan, plan_count, register_plan, retire_all_plans,
33};
34pub use scan::{MAX_NESTING, ScanError, scan_template};
35pub use synthesize::{synthesize, synthesize_indexed};
36pub use validate::{ArgKind, ValidationError, check_call, validate};
37
38/// Marker documenting that this crate is filled at Milestone 6.
39pub const FILLED_AT_MILESTONE: u32 = 6;
40
41/// Every name a parser expression may begin with: §7.4's atomics and §7.5's
42/// constructors, in their own tables' order.
43///
44/// The two are one list because they are one thing to a *user* — the word after
45/// `read` or inside `{…}` — and the two diagnostics for getting it wrong
46/// (`I010`, `I013`) are the same mistake seen from two tables. A "did you mean"
47/// that only knew one of them would answer `int` for `intt` and nothing for
48/// `line`.
49pub fn parser_names() -> impl Iterator<Item = &'static str> {
50    AtomicKind::ALL
51        .iter()
52        .map(|a| a.keyword())
53        .chain(Constructor::ALL.iter().map(|c| c.keyword()))
54}
55
56/// The atomic or constructor `name` was probably meant to be (ADR-132).
57///
58/// §15.3's own example: `line` answers `lines`. The threshold is
59/// [`praxis_source::nearest`]'s, shared with every other did-you-mean in the
60/// compiler, so one place decides when a near miss is near enough.
61#[must_use]
62pub fn nearest_parser_name(name: &str) -> Option<&'static str> {
63    praxis_source::nearest(name, parser_names())
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn milestone_marker_is_six() {
72        assert_eq!(FILLED_AT_MILESTONE, 6);
73    }
74}