praxis-input-parser 0.1.0

The Praxis `read` DSL: template parsing, type synthesis, and parser plans.
Documentation
//! The unified input parser DSL (§7, §14.1).
//!
//! Responsibility (per the design): the parser-expression typed AST, the backtick
//! template scanner, static validation, compile-time result-type synthesis, and
//! parser-plan construction. The DSL has its own typed AST (§7.9) and is **not**
//! lowered immediately into string-splitting calls.
//!
//! The crate covers the atomics ([`AtomicKind::ALL`] — `int`, `uint`, `float`,
//! `byte`, `char`, `digit`, `word`, `identifier`, `text`, `rest`), the
//! constructors ([`Constructor::ALL`] — `lines`, `sections`, `csv`, `ws`,
//! `sep`, `grid`, `matrix`, `chars`, `one_of`, `block`, `choice`, `optional`,
//! `scan`, `repeated`), backtick templates, type synthesis, and plan lowering.
//! The runtime interpreter lives in `praxis-runtime::parser`.

pub mod ast;
pub mod body;
pub mod call;
pub mod plan;
pub mod scan;
pub mod synthesize;
pub mod validate;

pub use ast::{
    ArgShape, AtomicKind, BlockItem, CaptureName, Constructor, EmptySeparator, InvalidCaptureName,
    InvalidRepeatCount, ParserAst, RepeatCount, SectionItem, Separator, SkipPolicy, TemplatePart,
    WsPolicy,
};
pub use call::{CallArg, build_call, build_repeated_tail};
pub use plan::{
    BlockItemNode, CompiledPlan, FieldOrder, MAX_PLANS, ParserPlan, PlanId, PlanNode,
    SectionItemNode, SourceOrder, TemplatePartNode, TemplateShape, TooManyPlans, get_plan,
    lower_to_plan, plan_count, register_plan, retire_all_plans,
};
pub use scan::{MAX_NESTING, ScanError, scan_template};
pub use synthesize::{synthesize, synthesize_indexed};
pub use validate::{ArgKind, ValidationError, check_call, validate};

/// Marker documenting that this crate is filled at Milestone 6.
pub const FILLED_AT_MILESTONE: u32 = 6;

/// Every name a parser expression may begin with: §7.4's atomics and §7.5's
/// constructors, in their own tables' order.
///
/// The two are one list because they are one thing to a *user* — the word after
/// `read` or inside `{…}` — and the two diagnostics for getting it wrong
/// (`I010`, `I013`) are the same mistake seen from two tables. A "did you mean"
/// that only knew one of them would answer `int` for `intt` and nothing for
/// `line`.
pub fn parser_names() -> impl Iterator<Item = &'static str> {
    AtomicKind::ALL
        .iter()
        .map(|a| a.keyword())
        .chain(Constructor::ALL.iter().map(|c| c.keyword()))
}

/// The atomic or constructor `name` was probably meant to be (ADR-132).
///
/// §15.3's own example: `line` answers `lines`. The threshold is
/// [`praxis_source::nearest`]'s, shared with every other did-you-mean in the
/// compiler, so one place decides when a near miss is near enough.
#[must_use]
pub fn nearest_parser_name(name: &str) -> Option<&'static str> {
    praxis_source::nearest(name, parser_names())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn milestone_marker_is_six() {
        assert_eq!(FILLED_AT_MILESTONE, 6);
    }
}