tamer: parser::ParseState::delegate_lookahead: New concept

This introduces a new method similar to the previous `delegate`, but with
another closure that allows for handling lookahead tokens from the child
parser.

Admittedly, this isn't exactly what I was going for---a list of arguments
isn't exactly self-documenting, especially with the brevity when the
arguments line up---but this was easy to do and so I'll run with this for
now.

This also modified `delegate` to accept a context, even though it wasn't
necessary, both for consistency with its lookup counterpart and for brevity
with the `into` argument (allowing, in our case, to just pass the name of
the variant, rather than a closure).

I'm not going to handle the actual starting and accepting state stitching
abstraction for now; I'd like to observe future boilerplate more before I
consider the best way to handle it, though I do have some ideas.

DEV-10863
main
Mike Gerwitz 2022-03-29 14:18:08 -04:00
parent 2a3d5be159
commit 4cb478a42d
3 changed files with 56 additions and 29 deletions

View File

@ -132,7 +132,6 @@ impl<SS: XmloSymtableState> ParseState for XmloReaderState<SS> {
type Error = XmloError;
fn parse_token(self, tok: Self::Token) -> TransitionResult<Self> {
use ParseStatus::{Dead, Incomplete, Object as Obj};
use XmloReaderState::*;
match (self, tok) {
@ -172,9 +171,7 @@ impl<SS: XmloSymtableState> ParseState for XmloReaderState<SS> {
// TOOD: It'd be nice to augment errors with the symbol table
// span as well (e.g. "while processing symbol table at <loc>").
(Symtable(span, ss), tok) => {
ss.delegate(tok, |ss| Symtable(span, ss))
}
(Symtable(span, ss), tok) => ss.delegate(span, tok, Symtable),
todo => todo!("{todo:?}"),
}
@ -187,7 +184,7 @@ impl<SS: XmloSymtableState> ParseState for XmloReaderState<SS> {
/// Symbol table parser operating within a delimited context.
///
/// This parser expects a parent [`ParserState`] to indicate when symtable
/// This parser expects a parent [`ParseState`] to indicate when symtable
/// parsing ought to start and end—
/// this parser does not recognize any opening or closing tags.
#[derive(Debug, Default, PartialEq, Eq)]

View File

@ -155,7 +155,7 @@ pub trait ParseState: Default + PartialEq + Eq + Debug {
/// or it is acceptable to parse all the way until the end.
fn is_accepting(&self) -> bool;
/// Delegate parsing to a compatible, stitched [`ParseState`].
/// Delegate parsing from a compatible, stitched [`ParseState`]~`SP`.
///
/// This helps to combine two state machines that speak the same input
/// language
@ -170,12 +170,15 @@ pub trait ParseState: Default + PartialEq + Eq + Debug {
///
/// This assumes that no lookahead token from [`ParseStatus::Dead`] will
/// need to be handled by the parent state~`SP`.
/// To handle a token of lookahead,
/// use [`Self::delegate_lookahead`] instead.
///
/// _TODO: More documentation once this is finalized._
fn delegate<SP>(
fn delegate<C, SP>(
self,
context: C,
tok: Self::Token,
into: impl FnOnce(Self) -> SP,
into: impl FnOnce(C, Self) -> SP,
) -> TransitionResult<SP>
where
SP: ParseState<Token = Self::Token>,
@ -186,13 +189,47 @@ pub trait ParseState: Default + PartialEq + Eq + Debug {
let (Transition(newst), result) = self.parse_token(tok).into();
Transition(into(newst)).result(match result {
// This does not use `delegate_lookahead` so that we can have
// `into: impl FnOnce` instead of `Fn`.
Transition(into(context, newst)).result(match result {
Ok(Incomplete) => Ok(Incomplete),
Ok(Obj(obj)) => Ok(Obj(obj.into())),
Ok(Dead(tok)) => Ok(Dead(tok)),
Err(e) => Err(e.into()),
})
}
/// Delegate parsing from a compatible, stitched [`ParseState`]~`SP` with
/// support for a lookahead token.
///
/// This does the same thing as [`Self::delegate`],
/// but allows for the handling of a lookahead token from [`Self`]
/// rather than simply proxying [`ParseStatus::Dead`].
///
/// _TODO: More documentation once this is finalized._
fn delegate_lookahead<C, SP>(
self,
context: C,
tok: Self::Token,
into: impl FnOnce(C, Self) -> SP,
lookahead: impl FnOnce(C, Self, Self::Token) -> TransitionResult<SP>,
) -> TransitionResult<SP>
where
SP: ParseState<Token = Self::Token>,
Self::Object: Into<<SP as ParseState>::Object>,
Self::Error: Into<<SP as ParseState>::Error>,
{
use ParseStatus::{Dead, Incomplete, Object as Obj};
let (Transition(newst), result) = self.parse_token(tok).into();
match result {
Ok(Incomplete) => Transition(into(context, newst)).incomplete(),
Ok(Obj(obj)) => Transition(into(context, newst)).ok(obj.into()),
Ok(Dead(tok)) => lookahead(context, newst, tok),
Err(e) => Transition(into(context, newst)).err(e),
}
}
}
/// Result of applying a [`Token`] to a [`ParseState`].

View File

@ -44,8 +44,7 @@ use super::{
};
use crate::{
parse::{
self, ParseState, ParseStatus, ParsedResult, Token, Transition,
TransitionResult,
self, ParseState, ParsedResult, Token, Transition, TransitionResult,
},
span::Span,
sym::SymbolId,
@ -157,6 +156,12 @@ impl Display for Object {
}
}
impl From<Attr> for Object {
fn from(attr: Attr) -> Self {
Self::Attr(attr)
}
}
/// XIRF-compatible attribute parser.
pub trait FlatAttrParseState = ParseState<Token = XirToken, Object = Attr>
where
@ -200,7 +205,6 @@ where
type Error = StateError;
fn parse_token(self, tok: Self::Token) -> TransitionResult<Self> {
use ParseStatus::{Dead, Incomplete, Object as Obj};
use State::{AttrExpected, Done, NodeExpected, PreRoot};
match (self, tok) {
@ -219,23 +223,12 @@ where
(NodeExpected(stack), tok) => Self::parse_node(stack, tok),
(AttrExpected(stack, sa), tok) => {
match sa.parse_token(tok).into() {
(Transition(sa), Ok(Incomplete)) => {
Transition(AttrExpected(stack, sa)).incomplete()
}
(Transition(sa), Ok(Obj(attr))) => {
Transition(AttrExpected(stack, sa))
.ok(Object::Attr(attr))
}
(_, Ok(Dead(lookahead))) => {
Self::parse_node(stack, lookahead)
}
(Transition(sa), Err(x)) => {
Transition(AttrExpected(stack, sa)).err(x)
}
}
}
(AttrExpected(stack, sa), tok) => sa.delegate_lookahead(
stack,
tok,
AttrExpected,
|stack, _, lookahead| Self::parse_node(stack, lookahead),
),
(Done, tok) => Transition(Done).dead(tok),
}