Parser error recovery in `syn` for better IDE support with proc-macros
This is tangential, but I just found this really interesting parser API that I thought I'd share.
The core idea is that diagnostics, parser-to-input alignment and output are all independently optional when a recovering parser returns:
// example: tuple.rs
impl<T0, T1, T2, Last> PopParsedFrom for (T0, T1, T2, Last)
where
T0: PopParsedFrom,
T1: PopParsedFrom,
T2: PopParsedFrom,
Last: PopParsedFrom,
{
// (Enables parser combinators.)
type Parsed = (T1::Parsed, T2::Parsed, T3::Parsed, Last::Parsed);
fn pop_parsed_from(
input: &mut crate::Input,
errors: &mut crate::Errors, // not used for control flow
) -> Parsed<Self::Parsed> {
// This could also be written with a `Parsed::zip`,
// so imagine a more complicated parser here.
let t = T0::pop_parsed_from(input, errors)?
.zip(T1::pop_parsed_from(input, errors)?)
.zip(T2::pop_parsed_from(input, errors)?);
);
Last::pop_parsed_from(input, errors).map_some_and(|last| {
t.map(|((t0, t1), t2)| (t0, t1, t2, last))
})
}
}
// parsed.rs
#![try_trait_v2]
struct Parsed<T> {
/// Determines branching.
pub aligned: bool,
/// Unwrapped by `?` iff `aligned`.
pub output: Option<T>,
}
impl<T> FromResidual for Parsed<T> {
fn from_residual(_: <Self as Try>::Residual) -> Self {
Self { aligned: false, output: None }
}
}
impl<T> Try for Parsed<T> {
type Output = Option<T>;
type Residual = Option<T>; // Might be useful for something.
fn from_output(output: Self::Output) -> Self {
Self { aligned: true, output }
}
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
if self.aligned {
ControlFlow::Continue(self.output);
} else {
ControlFlow::Break(self.output);
}
}
}
(At least I think I can refer to non-associated Parsed unambiguously in the return type.)
For example, delimited groups, separated repeats and "end of input" can realign the parser.
Optionals, repeats and most punctuation (via Default) can always have Some output.
The nested tuple there is most likely the most concise way to write this without macros.
I think muncher-less macro_rules! implementations will also be possible with some chaff.
Since try_trait_v2 (#84277) stabilisation seems far off, I'm currently emulating this with
with try_trait_v2 |
stable |
|---|---|
Parsed<Self::Parsed> |
ControlFlow<Option<Self::Parsed>, Option<Self::Parsed>> |
? |
`.map_break( |
….map_some_and(…)[1] |
match … { ControlFlow::… => …, ControlFlow::… => … } |
which makes it somewhat clunky.
- Name pending. ↩︎
Discussion in the ATmosphere