increparse_nom/lib.rs
1//! Wrap [`nom`] parsers in increparse [`Pass`](increparse::Pass)es.
2//!
3//! nom reports offsets relative to the input slice it was handed; increparse
4//! needs *absolute* spans carrying a source revision — and the incremental
5//! tree only reuses regions whose spans match exactly. Getting that
6//! translation wrong by one byte silently degrades re-parsing into
7//! re-parsing-everything. This crate does it once, correctly, so your nom
8//! parsers can stay in slice-relative coordinates.
9//!
10//! A pass is a function from a [`LocatedSpan`] of the region's text to an
11//! `IResult` whose output is the parsed children: each child is a
12//! **slice-relative** `Range<usize>` plus a context value for the next
13//! round. The wrapper converts the result to an
14//! [`Outcome`](increparse::Outcome):
15//!
16//! * `Ok(children)` → `Outcome::Expand` with rebased absolute spans,
17//! * `Ok(vec![])` → `Outcome::Done` (nothing left to parse in the region),
18//! * `Err(_)` → `Outcome::Failed` (the error is dropped; the region stays
19//! in the tree as a leaf and later passes may retry it).
20//!
21//! # Examples
22//!
23//! ```
24//! use increparse::{Outcome, Pass, Span};
25//! use increparse_nom::{nom_pass, NomChildren};
26//! use nom::IResult;
27//! use nom::bytes::complete::tag;
28//! use nom_locate::LocatedSpan;
29//! use std::ops::Range;
30//!
31//! fn split_at_hi(i: LocatedSpan<&str>) -> IResult<LocatedSpan<&str>, NomChildren<()>> {
32//! let (i, _) = tag("hi")(i)?;
33//! let end = i.location_offset();
34//! Ok((i, vec![(0..end, ())]))
35//! }
36//!
37//! let pass = nom_pass(split_at_hi);
38//! let source = "hi there";
39//! match pass.parse(source, Span::new(0, source.len(), 0), &()) {
40//! Outcome::Expand(children) => {
41//! // The child span is absolute, even though nom saw only a slice.
42//! assert_eq!(children[0].0, Span::new(0, 2, 0));
43//! }
44//! _ => panic!("expected expansion"),
45//! }
46//! ```
47//!
48//! Spans you emit are validated by the engine like any other: they must be
49//! contained in the region and (unless
50//! [`EngineConfig::enforce_shrink`](increparse::EngineConfig::enforce_shrink)
51//! is disabled) strictly smaller.
52
53#![forbid(unsafe_code)]
54#![deny(missing_docs)]
55
56use increparse::{Outcome, Pass, Span};
57use nom::IResult;
58use nom_locate::LocatedSpan;
59use std::ops::Range;
60
61/// The children a nom parser produces: slice-relative ranges plus the
62/// context each child carries into the next round.
63pub type NomChildren<C> = Vec<(Range<usize>, C)>;
64
65/// The input and remaining-output type nom parsers see: a located view of
66/// the region's slice of the source.
67pub type Located<'a> = LocatedSpan<&'a str>;
68
69/// A [`Pass`] driven by a nom parser.
70///
71/// See the [crate docs](self) for the conversion rules. Build one with
72/// [`nom_pass`].
73#[derive(Debug, Clone, Copy)]
74pub struct NomPass<F> {
75 f: F,
76}
77
78/// Creates a pass from a nom parser producing [`NomChildren`].
79pub fn nom_pass<C, F>(f: F) -> NomPass<F>
80where
81 F: for<'a> Fn(Located<'a>) -> IResult<Located<'a>, NomChildren<C>>,
82{
83 NomPass { f }
84}
85
86impl<C, F> Pass for NomPass<F>
87where
88 F: for<'a> Fn(Located<'a>) -> IResult<Located<'a>, NomChildren<C>> + Send + Sync,
89{
90 type Ctx = C;
91
92 fn parse(&self, source: &str, span: Span, _ctx: &C) -> Outcome<C> {
93 let slice = &source[span.to_range()];
94 let input = LocatedSpan::new(slice);
95 match (self.f)(input) {
96 Ok((_rest, children)) => {
97 if children.is_empty() {
98 return Outcome::Done;
99 }
100 let children = children
101 .into_iter()
102 .map(|(range, ctx)| {
103 (
104 Span::new(span.start + range.start, span.start + range.end, span.rev),
105 ctx,
106 )
107 })
108 .collect();
109 Outcome::Expand(children)
110 }
111 Err(_) => Outcome::Failed,
112 }
113 }
114
115 fn name(&self) -> &'static str {
116 "NomPass"
117 }
118}