tamer: parse::ParseState::delegate: Initial state stitching concept

This is the delegation portion of what I've come to call "state
stitching"---wiring together two state machines that recognize the same
input tokens.

This handles the delegation of tokens once the parser has been entered, but
does not yet handle the actual stitching part of it: wiring the start and
accepting states of the child parser to the parent.

This is indirectly tested by the XmloReader, but it will receive its own
tests once I further finalize this concept.  I'm playing around with some
ideas.  With that said, a quick visual inspection together with the
guarantees provided by the type system should convince any familiar reader
of its correctness.

DEV-10863
main
Mike Gerwitz 2022-03-29 12:46:16 -04:00
parent df05a71508
commit 2a3d5be159
2 changed files with 43 additions and 16 deletions

View File

@ -170,23 +170,11 @@ impl<SS: XmloSymtableState> ParseState for XmloReaderState<SS> {
Transition(Done).incomplete()
}
// TODO: This is all boilerplate; abstract away state stitching.
// 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) => match ss.parse_token(tok).into() {
(Transition(ss), Ok(Incomplete)) => {
Transition(Symtable(span, ss)).incomplete()
}
(Transition(ss), Ok(Obj(obj))) => {
Transition(Symtable(span, ss)).ok(Self::Object::from(obj))
}
(Transition(ss), Ok(Dead(tok))) => {
Transition(Symtable(span, ss)).dead(tok)
}
(Transition(ss), Err(e)) => {
Transition(Symtable(span, ss)).err(e)
}
},
(Symtable(span, ss), tok) => {
ss.delegate(tok, |ss| Symtable(span, ss))
}
todo => todo!("{todo:?}"),
}
@ -277,7 +265,7 @@ impl ParseState for SymtableState {
impl From<(SymbolId, SymAttrs, Span)> for XmloEvent {
fn from(tup: (SymbolId, SymAttrs, Span)) -> Self {
match tup {
(sym, attrs, span) => Self::SymDecl(sym, attrs, span)
(sym, attrs, span) => Self::SymDecl(sym, attrs, span),
}
}
}

View File

@ -154,6 +154,45 @@ pub trait ParseState: Default + PartialEq + Eq + Debug {
/// It is acceptable to attempt to parse just one of those attributes,
/// or it is acceptable to parse all the way until the end.
fn is_accepting(&self) -> bool;
/// Delegate parsing to a compatible, stitched [`ParseState`].
///
/// This helps to combine two state machines that speak the same input
/// language
/// (share the same [`Self::Token`]),
/// handling the boilerplate of delegating [`Self::Token`] from a
/// parent state~`SP` to `Self`.
///
/// Token delegation happens after [`Self`] has been entered from a
/// parent [`ParseState`] context~`SP`,
/// so stitching the start and accepting states must happen elsewhere
/// (for now).
///
/// This assumes that no lookahead token from [`ParseStatus::Dead`] will
/// need to be handled by the parent state~`SP`.
///
/// _TODO: More documentation once this is finalized._
fn delegate<SP>(
self,
tok: Self::Token,
into: impl FnOnce(Self) -> 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();
Transition(into(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()),
})
}
}
/// Result of applying a [`Token`] to a [`ParseState`].